diff --git a/assets/proj4/projections/adams_hemi.rst b/assets/proj4/projections/adams_hemi.rst new file mode 100644 index 00000000..96fe78f1 --- /dev/null +++ b/assets/proj4/projections/adams_hemi.rst @@ -0,0 +1,42 @@ +.. _adams_hemi: + +******************************************************************************** +Adams Hemisphere in a Square +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | adams_hemi | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/adams_hemi.png + :width: 500 px + :align: center + :alt: Adams Hemisphere in a Square + + proj-string: ``+proj=adams_hemi`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/adams_ws1.rst b/assets/proj4/projections/adams_ws1.rst new file mode 100644 index 00000000..3283ff57 --- /dev/null +++ b/assets/proj4/projections/adams_ws1.rst @@ -0,0 +1,42 @@ +.. _adams_ws1: + +******************************************************************************** +Adams World in a Square I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | adams_ws1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/adams_ws1.png + :width: 500 px + :align: center + :alt: Adams World in a Square I + + proj-string: ``+proj=adams_ws1`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/adams_ws2.rst b/assets/proj4/projections/adams_ws2.rst new file mode 100644 index 00000000..60630286 --- /dev/null +++ b/assets/proj4/projections/adams_ws2.rst @@ -0,0 +1,42 @@ +.. _adams_ws2: + +******************************************************************************** +Adams World in a Square II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | adams_ws2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/adams_ws2.png + :width: 500 px + :align: center + :alt: Adams World in a Square II + + proj-string: ``+proj=adams_ws2`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/aea.rst b/assets/proj4/projections/aea.rst new file mode 100644 index 00000000..47a24251 --- /dev/null +++ b/assets/proj4/projections/aea.rst @@ -0,0 +1,52 @@ +.. _aea: + +******************************************************************************** +Albers Equal Area +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Conic | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | aea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/aea.png + :width: 500 px + :align: center + :alt: Albers Equal Area + + proj-string: ``+proj=aea +lat_1=29.5 +lat_2=42.5`` + +Options +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + diff --git a/assets/proj4/projections/aeqd.rst b/assets/proj4/projections/aeqd.rst new file mode 100644 index 00000000..2afadd12 --- /dev/null +++ b/assets/proj4/projections/aeqd.rst @@ -0,0 +1,51 @@ +.. _aeqd: + +******************************************************************************** +Azimuthal Equidistant +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | aeqd | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/aeqd.png + :width: 500 px + :align: center + :alt: Azimuthal Equidistant + + proj-string: ``+proj=aeqd`` + + +Options +################################################################################ + +.. note:: All options are optional for the Azimuthal Equidistant projection. + +.. option:: +guam + + Use Guam ellipsoidal formulas. Only accurate near the Island of Guam + (:math:`\lambda\approx 144.5^{\circ}`, :math:`\phi\approx 13.5^{\circ}`) + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst diff --git a/assets/proj4/projections/airy.rst b/assets/proj4/projections/airy.rst new file mode 100644 index 00000000..04af065e --- /dev/null +++ b/assets/proj4/projections/airy.rst @@ -0,0 +1,58 @@ +.. _airy: + +******************************************************************************** +Airy +******************************************************************************** + +The Airy projection is an azimuthal minimum error projection for the region +within the small or great circle defined by an angular distance, +:math:`\phi_b`, from the tangency point of the plane :math:`( \lambda_0, \phi_0 )`. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | airy | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + + +.. figure:: ./images/airy.png + :width: 500 px + :align: center + :alt: Airy + + proj-string: ``+proj=airy`` + +Options +################################################################################ + +.. option:: +lat_b + + Angular distance from tangency point of the plane :math:`( \lambda_0, \phi_0 )` + where the error is kept at minimum. + + *Defaults to 90° (suitable for hemispherical maps).* + +.. option:: +no_cut + + Do not cut at hemisphere limit + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/R.rst diff --git a/assets/proj4/projections/aitoff.rst b/assets/proj4/projections/aitoff.rst new file mode 100644 index 00000000..7508445d --- /dev/null +++ b/assets/proj4/projections/aitoff.rst @@ -0,0 +1,40 @@ +.. _aitoff: + +******************************************************************************** +Aitoff +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | aitoff | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/aitoff.png + :width: 500 px + :align: center + :alt: Aitoff + + proj-string: ``+proj=aitoff`` + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/alsk.rst b/assets/proj4/projections/alsk.rst new file mode 100644 index 00000000..5c2b416f --- /dev/null +++ b/assets/proj4/projections/alsk.rst @@ -0,0 +1,39 @@ +.. _alsk: + +******************************************************************************** +Modified Stereographic of Alaska +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Modified azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Alaska | ++---------------------+----------------------------------------------------------+ +| **Alsk** | alsk | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/alsk.png + :width: 500 px + :align: center + :alt: Modified Stereographic of Alaska + + proj-string: ``+proj=alsk`` + +Options +################################################################################ + +.. note:: All options are optional for the projection. + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst diff --git a/assets/proj4/projections/apian.rst b/assets/proj4/projections/apian.rst new file mode 100644 index 00000000..870dc0ce --- /dev/null +++ b/assets/proj4/projections/apian.rst @@ -0,0 +1,44 @@ +.. _apian: + +******************************************************************************** +Apian Globular I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | apian | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/apian.png + :width: 500 px + :align: center + :alt: Apian Globular I + + proj-string: ``+proj=apian`` + +Options +################################################################################ + +.. note:: All options are optional for the Apian Globular projection. + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/august.rst b/assets/proj4/projections/august.rst new file mode 100644 index 00000000..ae5c95dc --- /dev/null +++ b/assets/proj4/projections/august.rst @@ -0,0 +1,42 @@ +.. _august: + +******************************************************************************** +August Epicycloidal +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | august | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/august.png + :width: 500 px + :align: center + :alt: August Epicycloidal + + proj-string: ``+proj=august`` + +Parameters +################################################################################ + +.. note:: All options are optional for the August Epicycloidal projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/bacon.rst b/assets/proj4/projections/bacon.rst new file mode 100644 index 00000000..8e2fda9e --- /dev/null +++ b/assets/proj4/projections/bacon.rst @@ -0,0 +1,42 @@ +.. _bacon: + +******************************************************************************** +Bacon Globular +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | bacon | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/bacon.png + :width: 500 px + :align: center + :alt: Bacon Globular + + proj-string: ``+proj=bacon`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Bacon Globular projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/bertin1953.rst b/assets/proj4/projections/bertin1953.rst new file mode 100644 index 00000000..bbed0c80 --- /dev/null +++ b/assets/proj4/projections/bertin1953.rst @@ -0,0 +1,64 @@ +.. _bertin1953: + +******************************************************************************** +Bertin 1953 +******************************************************************************** + +.. versionadded:: 6.0.0 + ++---------------------+-------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+-------------------------------+ +| **Available forms** | Forward, spherical projection | ++---------------------+-------------------------------+ +| **Defined area** | Global | ++---------------------+-------------------------------+ +| **Alias** | bertin1953 | ++---------------------+-------------------------------+ +| **Domain** | 2D | ++---------------------+-------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+-------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+-------------------------------+ + + + +.. figure:: ./images/bertin1953.png + :width: 500 px + :align: center + :alt: Bertin 1953 + + proj-string: ``+proj=bertin1953`` + + +The Bertin 1953 projection is intended for making world maps. Created by Jacques Bertin +in 1953, this projection was the go-to choice of the French cartographic school when they +wished to represent phenomena on a global scale. The projection was formulated in 2017 +by Philippe Rivière for visionscarto.net. + + +Usage +############################################################################### + +The Bertin 1953 projection has no special options. Its rotation parameters +are fixed. Here is an example of a forward projection with scale 1:: + + $ echo 122 47 | proj +proj=bertin1953 +R=1 + 0.72 0.73 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. Philippe Rivière (2017). `Bertin Projection (1953) `, Visionscarto.net. diff --git a/assets/proj4/projections/bipc.rst b/assets/proj4/projections/bipc.rst new file mode 100644 index 00000000..9ac73750 --- /dev/null +++ b/assets/proj4/projections/bipc.rst @@ -0,0 +1,46 @@ +.. _bipc: + +******************************************************************************** +Bipolar conic of western hemisphere +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | bipc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/bipc.png + :width: 500 px + :align: center + :alt: Bipolar conic of western hemisphere + + proj-string: ``+proj=bipc +ns`` + +Parameters +################################################################################ + +.. note:: All options are optional for the Bipolar Conic projection. + +.. option:: +ns + + Return non-skewed cartesian coordinates. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/boggs.rst b/assets/proj4/projections/boggs.rst new file mode 100644 index 00000000..82f43733 --- /dev/null +++ b/assets/proj4/projections/boggs.rst @@ -0,0 +1,42 @@ +.. _boggs: + +******************************************************************************** +Boggs Eumorphic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | boggs | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/boggs.png + :width: 500 px + :align: center + :alt: Boggs Eumorphic + + proj-string: ``+proj=boggs`` + +Parameters +################################################################################ + +.. note:: All options are optional for the Boggs Eumorphic projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/bonne.rst b/assets/proj4/projections/bonne.rst new file mode 100644 index 00000000..1b81237b --- /dev/null +++ b/assets/proj4/projections/bonne.rst @@ -0,0 +1,50 @@ +.. _bonne: + +******************************************************************************** +Bonne (Werner lat_1=90) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | bonne | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/bonne.png + :width: 500 px + :align: center + :alt: Bonne (Werner lat_1=90) + + proj-string: ``+proj=bonne +lat_1=10`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/calcofi.rst b/assets/proj4/projections/calcofi.rst new file mode 100644 index 00000000..d72414a4 --- /dev/null +++ b/assets/proj4/projections/calcofi.rst @@ -0,0 +1,107 @@ +.. _calcofi: + +******************************************************************************** +Cal Coop Ocean Fish Invest Lines/Stations +******************************************************************************** + +The CalCOFI pseudo-projection is the line and station coordinate system of the +California Cooperative Oceanic Fisheries Investigations program, known as CalCOFI, for sampling offshore of the west coast of the U.S. and Mexico. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Only valid for the west coast of USA and Mexico | ++---------------------+----------------------------------------------------------+ +| **Alias** | calcofi | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + + +.. image:: ../../../images/calcofi.png + :width: 500 px + :align: center + :alt: Cal Coop Ocean Fish Invest Lines/Stations + +The coordinate system is based on the Mercator projection with units rotated -30 +degrees from the meridian so that they are oriented with the coastline of the Southern California Bight and Baja California. +Lines increase from Northwest to Southeast. +A unit of line is 12 nautical miles. Stations increase from inshore to offshore. +A unit of station is equal to 4 nautical miles. +The rotation point is located at line 80, station 60, or 34.15 degrees N, -121.15 degrees W, and is depicted by the red dot in the figure. +By convention, the ellipsoid of Clarke 1866 is used to calculate CalCOFI coordinates. + +The CalCOFI program is a joint research effort by the U.S. National Oceanic and +Atmospheric Administration, University of California Scripps Oceanographic Institute, and California Department of Fish and Game. +Surveys have been conducted for the CalCOFI program since 1951, creating one of the oldest and most scientifically valuable joint oceanographic and fisheries data sets in the world. +The CalCOFI line and station coordinate system is now used by several other programs including the Investigaciones Mexicanas de la Corriente de California (IMECOCAL) program offshore of Baja California. +The figure depicts some commonly sampled locations from line 40 to line 156.7 and offshore to station 120. Blue numbers indicate line (bottom) or station (left) numbers along the grid. Note that lines spaced at approximate 3-1/3 intervals are commonly sampled, e.g., lines 43.3 and 46.7. + +Usage +############################################################################### + +A typical forward CalCOFI projection would be from long/lat coordinates on the +Clark 1866 ellipsoid. +For example:: + + proj +proj=calcofi +ellps=clrk66 -E <`_ +#. `The Investigaciones Mexicanas de la Corriente de California `_ + + + + + + + + + + + + + + diff --git a/assets/proj4/projections/cass.rst b/assets/proj4/projections/cass.rst new file mode 100644 index 00000000..fb3b3576 --- /dev/null +++ b/assets/proj4/projections/cass.rst @@ -0,0 +1,178 @@ +.. _cass: + +******************************************************************************** +Cassini (Cassini-Soldner) +******************************************************************************** + +Although the Cassini projection has been largely replaced by the Transverse Mercator, it is still in limited use outside the United States and was one of the major topographic mapping projections until the early 20th century. + ++---------------------+-------------------------------------------------------------------------+ +| **Classification** | Transverse and oblique cylindrical | ++---------------------+-------------------------------------------------------------------------+ +| **Available forms** | Forward and inverse, Spherical and ellipsoidal | ++---------------------+-------------------------------------------------------------------------+ +| **Defined area** | Global, but best used near the central meridian with long, narrow areas | ++---------------------+-------------------------------------------------------------------------+ +| **Alias** | cass | ++---------------------+-------------------------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+-------------------------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+-------------------------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+-------------------------------------------------------------------------+ + + +.. figure:: ./images/cass.png + :width: 500 px + :align: center + :alt: Cassini + + proj-string: ``+proj=cass`` + +Usage +##### + +There has been little usage of the spherical version of the Cassini, but the ellipsoidal Cassini-Soldner version was adopted by the Ordnance Survey for the official survey of Great Britain during the second half of the 19th century :cite:`Steers1970`. +Many of these maps were prepared at a scale of 1:2,500. +The Cassini-Soldner was also used for the detailed mapping of many German states during the same period. + + +Example using EPSG 30200 (Trinidad 1903, units in clarke's links):: + + $ echo 0.17453293 -1.08210414 | proj +proj=cass \ + +lat_0=10.44166666666667 +lon_0=-61.33333333333334 \ + +x_0=86501.46392051999 +y_0=65379.0134283 \ + +a=6378293.645208759 +b=6356617.987679838 \ + +to_meter=0.201166195164 + 66644.94 82536.22 + +Example using EPSG 3068 (Soldner Berlin):: + + $ echo 13.5 52.4 | proj +proj=cass \ + +lat_0=52.41864827777778 +lon_0=13.62720366666667 \ + +x_0=40000 +y_0=10000 +ellps=bessel + 31343.05 7932.76 + +Options +################################################################################ + +.. note:: All options are optional for the Cassini projection. + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. option:: +hyperbolic + + Use modified form of the standard Cassini-Soldner projection known as the Hyperbolic Cassini-Soldner. + This is used in particular for the "Vanua Levu Grid" of the island of Vanua Levu, Fiji (EPSG:3139) + + +Mathematical definition +####################### + +The formulas describing the Cassini projection are taken from :cite:`Snyder1987`. + +:math:`\phi_0` is the latitude of origin that match the center of the map (default to 0). It can be set with ``+lat_0``. + + +Spherical form +============== + +Forward projection +------------------ + +.. math:: + + x = \arcsin(\cos(\phi) \sin(\lambda)) + +.. math:: + + y = \arctan2(\tan(\phi), \cos(\lambda)) - \phi_0 + +Inverse projection +------------------ + +.. math:: + + \phi = \arcsin(\sin(y+\phi_0) \cos(x)) + +.. math:: + + \lambda = \arctan2(\tan(x), \cos(y+\phi_0)) + +Ellipsoidal form +================ + +Forward projection +------------------ + +.. math:: + + N = (1 - e^2 \sin^2(\phi))^{-1/2} + +.. math:: + + T = \tan^2(\phi) + +.. math:: + + A = \lambda \cos(\phi) + +.. math:: + + C = \frac{e^2}{1-e^2} cos^2(\phi) + +.. math:: + + x = N ( A - T \frac{A^3}{6} - (8-T+8C)T\frac{A^5}{120}) + +.. math:: + + y = M(\phi) - M(\phi_0) + N \tan(\phi)(\frac{A^2}{2} + (5-T+6C)\frac{A^4}{24}) + +and M() is the meridional distance function. + +Inverse projection +------------------ + +.. math:: + + \phi' = M^{-1}(M(\phi_0)+y) + +if :math:`\phi' = \frac{\pi}{2}` then :math:`\phi=\phi'` and :math:`\lambda=0` + +otherwise evaluate T and N above using :math:`\phi'` and + +.. math:: + + R = (1 - e^2)(1 - e^2 sin^2 \phi')^{-3/2} + +.. math:: + + D = x/N + +.. math:: + + \phi = \phi' - \tan \phi' \frac{N}{R}(\frac{D^2}{2}-(1+3T)\frac{D^4}{24}) + +.. math:: + + \lambda = \frac{(D - T\frac{D^3}{3} + (1+3T)T\frac{D^5}{15})}{\cos \phi'} + +.. _further-reading: + +Further reading +############### + +#. `Wikipedia `_ +#. `EPSG, POSC literature pertaining to Coordinate Conversions and Transformations including Formulas `_ diff --git a/assets/proj4/projections/cc.rst b/assets/proj4/projections/cc.rst new file mode 100644 index 00000000..c6f3927a --- /dev/null +++ b/assets/proj4/projections/cc.rst @@ -0,0 +1,42 @@ +.. _cc: + +******************************************************************************** +Central Cylindrical +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | cc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/cc.png + :width: 500 px + :align: center + :alt: Central Cylindrical + + proj-string: ``+proj=cc`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Central Cylindrical projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/ccon.rst b/assets/proj4/projections/ccon.rst new file mode 100644 index 00000000..949da8b5 --- /dev/null +++ b/assets/proj4/projections/ccon.rst @@ -0,0 +1,159 @@ +.. _ccon: + +******************************************************************************** +Central Conic +******************************************************************************** + +.. versionadded:: 5.0.0 + +This is central (centrographic) projection on cone tangent at :option:``lat_1`` +latitude, identical with ``conic()`` projection from ``mapproj`` R package. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conic | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but best used near the standard parallel | ++---------------------+----------------------------------------------------------+ +| **Alias** | ccon | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/ccon.png + :width: 500 px + :align: center + :alt: Central Conic + + proj-string: ``+proj=ccon +lat_1=52 +lon_0=19`` + +Usage +######## + +This simple projection is rarely used, as it is not equidistant, equal-area, nor +conformal. + +An example of usage (and the main reason to implement this projection in proj4) +is the ATPOL geobotanical grid of Poland, developed in Institute of Botany, +Jagiellonian University, Krakow, Poland in 1970s :cite:`Zajac1978`. The grid was +originally handwritten on paper maps and further copied by hand. The projection +(together with strange Earth radius) was chosen by its creators as the compromise +fit to existing maps during first software development in DOS era. Many years later +it is still de facto standard grid in Polish geobotanical research. + +The ATPOL coordinates can be achieved with with the following parameters: + +:: + + +proj=ccon +lat_1=52 +lon_0=19 +axis=esu +a=6390000 +x_0=330000 +y_0=-350000 + +For more information see :cite:`Komsta2016` and :cite:`Verey2017`. + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. option:: +lat_1= + + Standard parallel of projection. + +Optional +------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + + +Mathematical definition +################################################################################ + + +Forward projection +------------------------------------------------------------------------------- + +.. math:: + + r = \cot \phi_0 - \tan (\phi - \phi_0) + +.. math:: + + x = r \sin (\lambda\sin\phi_0) + +.. math:: + + y = \cot \phi_0 - r \cos (\lambda\sin\phi_0) + + +Inverse projection +------------------------------------------------------------------------------- + +.. math:: + + y = \cot \phi_0 - y + +.. math:: + + \phi = \phi_0 - \tan^{-1} ( \sqrt{x^2+y^2} - \cot \phi_0 ) + +.. math:: + + \lambda = \frac{\tan^{-1} \sqrt{x^2+y^2}}{\sin \phi_0} + +Reference values +################################################################################ + +For ATPOL to WGS84 test, run the following script: + +:: + + #!/bin/bash + cat << EOF | src/cs2cs -v -f "%E" +proj=ccon +lat_1=52 +lat_0=52 +lon_0=19 +axis=esu +a=6390000 +x_0=330000 +y_0=-350000 +to +proj=longlat + 0 0 + 0 700000 + 700000 0 + 700000 700000 + 330000 350000 + EOF + +It should result with + +:: + + 1.384023E+01 5.503040E+01 0.000000E+00 + 1.451445E+01 4.877385E+01 0.000000E+00 + 2.478271E+01 5.500352E+01 0.000000E+00 + 2.402761E+01 4.875048E+01 0.000000E+00 + 1.900000E+01 5.200000E+01 0.000000E+00 + +Analogous script can be run for reverse test: + +:: + + cat << EOF | src/cs2cs -v -f "%E" +proj=longlat +to +proj=ccon +lat_1=52 +lat_0=52 +lon_0=19 +axis=esu +a=6390000 +x_0=330000 +y_0=-350000 + 24 55 + 15 49 + 24 49 + 19 52 + EOF + +and it should give the following results: + +:: + + 6.500315E+05 4.106162E+03 0.000000E+00 + 3.707419E+04 6.768262E+05 0.000000E+00 + 6.960534E+05 6.722946E+05 0.000000E+00 + 3.300000E+05 3.500000E+05 0.000000E+00 diff --git a/assets/proj4/projections/cea.rst b/assets/proj4/projections/cea.rst new file mode 100644 index 00000000..a36859e3 --- /dev/null +++ b/assets/proj4/projections/cea.rst @@ -0,0 +1,79 @@ +.. _cea: + +******************************************************************************** +Equal Area Cylindrical +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | cea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/cea.png + :width: 500 px + :align: center + :alt: Equal Area Cylindrical + + proj-string: ``+proj=cea`` + +Named specializations +################################################################################ + +The Equal Area Cylindrical projection is sometimes known under other names when +it is instantiated with particular values of the ``lat_ts`` parameter: + ++----------------------------------+------------+ +| **Name** | **lat_ts** | ++----------------------------------+------------+ +| Lambert cylindrical equal-area | 0 | ++----------------------------------+------------+ +| Behrmann | 30 | ++----------------------------------+------------+ +| Gall-Peters | 45 | ++----------------------------------+------------+ + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Equal Area Cylindrical projection. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. note:: + + ``lat_ts`` and ``k_0`` are mutually exclusive. If ``lat_ts`` + is specified, it is equivalent to setting ``k_0`` to + :math:`\frac{\cos \phi_{ts}}{\sqrt{1 - e^2 \sin^2 \phi_{ts}}}` + +Further reading +############### + +#. `Wikipedia: Lambert cylindrical equal-area `_ + +#. `Wikipedia: Gall-Peters `_ + +#. `Wikipedia: Behrmann `_ diff --git a/assets/proj4/projections/chamb.rst b/assets/proj4/projections/chamb.rst new file mode 100644 index 00000000..c33017a5 --- /dev/null +++ b/assets/proj4/projections/chamb.rst @@ -0,0 +1,72 @@ +.. _chamb: + +******************************************************************************** +Chamberlin Trimetric +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | chamb | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/chamb.png + :width: 500 px + :align: center + :alt: Chamberlin Trimetric + + proj-string: ``+proj=chamb +lat_1=10 +lon_1=30 +lon_2=40`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. note:: Control points should be oriented clockwise. + +.. option:: +lat_1= + + Latitude of the first control point. + +.. option:: +lon_1= + + Longitude of the first control point. + +.. option:: +lat_2= + + Latitude of the second control point. + +.. option:: +lon_2= + + Longitude of the second control point. + +.. option:: +lat_3= + + Latitude of the third control point. + +.. option:: +lon_3= + + Longitude of the third control point. + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/col_urban.rst b/assets/proj4/projections/col_urban.rst new file mode 100644 index 00000000..df2873dd --- /dev/null +++ b/assets/proj4/projections/col_urban.rst @@ -0,0 +1,49 @@ +.. _col_urban: + +******************************************************************************** +Colombia Urban +******************************************************************************** + +.. versionadded:: 7.2 + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse ellipsoidal projection | ++---------------------+----------------------------------------------------------+ +| **Alias** | col_urban | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +From :cite:`IOGP2018`: + + The capital cites of each department in Colombia use an urban projection for large + scale topographic mapping of the urban areas. It is based on a plane through + the origin at an average height for the area, eliminating the need for corrections + to engineering survey observations. + + proj-string: ``+proj=col_urban`` + +Parameters +################################################################################ + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. option:: +h_0= + + Projection plane origin height (in metre) + + *Defaults to 0.0.* diff --git a/assets/proj4/projections/collg.rst b/assets/proj4/projections/collg.rst new file mode 100644 index 00000000..f0ed215f --- /dev/null +++ b/assets/proj4/projections/collg.rst @@ -0,0 +1,43 @@ +.. _collg: + +******************************************************************************** +Collignon +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | collg | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/collg.png + :width: 500 px + :align: center + :alt: Collignon + + proj-string: ``+proj=collg`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Collignon projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + diff --git a/assets/proj4/projections/comill.rst b/assets/proj4/projections/comill.rst new file mode 100644 index 00000000..fa04ec18 --- /dev/null +++ b/assets/proj4/projections/comill.rst @@ -0,0 +1,46 @@ +.. _comill: + +******************************************************************************** +Compact Miller +******************************************************************************** + +The Compact Miller projection is a cylindrical map projection with a +height-to-width ratio of 0.6:1. + +See :cite:`Jenny2015` + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | comill | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/comill.png + :width: 500 px + :align: center + :alt: Compact Miller + + proj-string: ``+proj=comill`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/crast.rst b/assets/proj4/projections/crast.rst new file mode 100644 index 00000000..406c300a --- /dev/null +++ b/assets/proj4/projections/crast.rst @@ -0,0 +1,43 @@ +.. _crast: + +******************************************************************************** +Craster Parabolic (Putnins P4) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | crast | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/crast.png + :width: 500 px + :align: center + :alt: Craster Parabolic (Putnins P4) + + proj-string: ``+proj=crast`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Craster Parabolic projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + diff --git a/assets/proj4/projections/denoy.rst b/assets/proj4/projections/denoy.rst new file mode 100644 index 00000000..b2913379 --- /dev/null +++ b/assets/proj4/projections/denoy.rst @@ -0,0 +1,42 @@ +.. _denoy: + +******************************************************************************** +Denoyer Semi-Elliptical +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | denoy | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/denoy.png + :width: 500 px + :align: center + :alt: Denoyer Semi-Elliptical + + proj-string: ``+proj=denoy`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Denoyer Semi-Elliptical projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck1.rst b/assets/proj4/projections/eck1.rst new file mode 100644 index 00000000..28359977 --- /dev/null +++ b/assets/proj4/projections/eck1.rst @@ -0,0 +1,49 @@ +.. _eck1: + +******************************************************************************** +Eckert I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck1.png + :width: 500 px + :align: center + :alt: Eckert I + + proj-string: ``+proj=eck1`` + + +.. math:: + + x &= 2 \sqrt{2/3\pi} \lambda (1- |\phi|/\pi) + + y &= 2 \sqrt{2/3\pi}\phi + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert I projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck2.rst b/assets/proj4/projections/eck2.rst new file mode 100644 index 00000000..aaa1ccd5 --- /dev/null +++ b/assets/proj4/projections/eck2.rst @@ -0,0 +1,42 @@ +.. _eck2: + +******************************************************************************** +Eckert II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck2.png + :width: 500 px + :align: center + :alt: Eckert II + + proj-string: ``+proj=eck2`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert II projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck3.rst b/assets/proj4/projections/eck3.rst new file mode 100644 index 00000000..94ef3a33 --- /dev/null +++ b/assets/proj4/projections/eck3.rst @@ -0,0 +1,42 @@ +.. _eck3: + +******************************************************************************** +Eckert III +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck3 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck3.png + :width: 500 px + :align: center + :alt: Eckert III + + proj-string: ``+proj=eck3`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert III projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck4.rst b/assets/proj4/projections/eck4.rst new file mode 100644 index 00000000..e62a4fd8 --- /dev/null +++ b/assets/proj4/projections/eck4.rst @@ -0,0 +1,49 @@ +.. _eck4: + +******************************************************************************** +Eckert IV +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck4 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck4.png + :width: 500 px + :align: center + :alt: Eckert IV + + proj-string: ``+proj=eck4`` + +.. math:: + + x = \lambda(1+cos\phi) / \sqrt{ 2 + \pi } + +.. math:: + y = 2 \phi / \sqrt { 2 + \pi } + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert IV projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck5.rst b/assets/proj4/projections/eck5.rst new file mode 100644 index 00000000..cfc888ff --- /dev/null +++ b/assets/proj4/projections/eck5.rst @@ -0,0 +1,42 @@ +.. _eck5: + +******************************************************************************** +Eckert V +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck5 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck5.png + :width: 500 px + :align: center + :alt: Eckert V + + proj-string: ``+proj=eck5`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert V projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eck6.rst b/assets/proj4/projections/eck6.rst new file mode 100644 index 00000000..1026b555 --- /dev/null +++ b/assets/proj4/projections/eck6.rst @@ -0,0 +1,42 @@ +.. _eck6: + +******************************************************************************** +Eckert VI +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eck6 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eck6.png + :width: 500 px + :align: center + :alt: Eckert VI + + proj-string: ``+proj=eck6`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Eckert VI projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eqc.rst b/assets/proj4/projections/eqc.rst new file mode 100644 index 00000000..47184752 --- /dev/null +++ b/assets/proj4/projections/eqc.rst @@ -0,0 +1,131 @@ +.. _eqc: + +******************************************************************************** +Equidistant Cylindrical (Plate Carrée) +******************************************************************************** + +The simplest of all projections. Standard parallels (0° when omitted) may be specified that determine latitude of true scale (k=h=1). + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but best used near the equator | ++---------------------+----------------------------------------------------------+ +| **Alias** | eqc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eqc.png + :width: 500 px + :align: center + :alt: Equidistant Cylindrical (Plate Carrée) + + proj-string: ``+proj=eqc`` + +Usage +######## + +Because of the distortions introduced by this projection, it has little use in navigation or cadastral mapping and finds its main use in thematic mapping. +In particular, the plate carrée has become a standard for global raster datasets, such as Celestia and NASA World Wind, because of the particularly simple relationship between the position of an image pixel on the map and its corresponding geographic location on Earth. + +The following table gives special cases of the cylindrical equidistant projection. + ++---------------------------------------------------------+--------------------------+ +| Projection Name | (lat ts=) :math:`\phi_0` | ++=========================================================+==========================+ +| Plain/Plane Chart | 0° | ++---------------------------------------------------------+--------------------------+ +| Simple Cylindrical | 0° | ++---------------------------------------------------------+--------------------------+ +| Plate Carrée | 0° | ++---------------------------------------------------------+--------------------------+ +| Ronald Miller—minimum overall scale distortion | 37°30′ | ++---------------------------------------------------------+--------------------------+ +| E.Grafarend and A.Niermann | 42° | ++---------------------------------------------------------+--------------------------+ +| Ronald Miller—minimum continental scale distortion | 43°30′ | ++---------------------------------------------------------+--------------------------+ +| Gall Isographic | 45° | ++---------------------------------------------------------+--------------------------+ +| Ronald Miller Equirectangular | 50°30′ | ++---------------------------------------------------------+--------------------------+ +| E.Grafarend and A.Niermann minimum linear distortion | 61°7′ | ++---------------------------------------------------------+--------------------------+ + + +Example using EPSG 32662 (WGS84 Plate Carrée):: + + $ echo 2 47 | proj +proj=eqc +ellps=WGS84 + 222638.98 5232016.07 + +Example using Plate Carrée projection with true scale at latitude 30° and central meridian 90°W:: + + $ echo -88 30 | proj +proj=eqc +lat_ts=30 +lon_0=90w + 192811.01 3339584.72 + +Parameters +################################################################################ + + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +Mathematical definition +####################### + +The formulas describing the Equidistant Cylindrical projection are all taken from :cite:`Snyder1987`. + +:math:`\phi_{ts}` is the latitude of true scale, i.e., the standard parallel where the scale of the projection is true. It can be set with ``+lat_ts``. + +:math:`\phi_0` is the latitude of origin that match the center of the map. It can be set with ``+lat_0``. + + +Forward projection +================== + +.. math:: + + x = \lambda \cos \phi_{ts} + +.. math:: + + y = \phi - \phi_0 + +Inverse projection +================== + +.. math:: + + \lambda = x / cos \phi_{ts} + +.. math:: + + \phi = y + \phi_0 + + +Further reading +############### + +#. `Wikipedia `_ +#. `Wolfram Mathworld `_ + + diff --git a/assets/proj4/projections/eqdc.rst b/assets/proj4/projections/eqdc.rst new file mode 100644 index 00000000..881a9282 --- /dev/null +++ b/assets/proj4/projections/eqdc.rst @@ -0,0 +1,52 @@ +.. _eqdc: + +******************************************************************************** +Equidistant Conic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conic | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | eqdc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/eqdc.png + :width: 500 px + :align: center + :alt: Equidistant Conic + + proj-string: ``+proj=eqdc +lat_1=55 +lat_2=60`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/eqearth.rst b/assets/proj4/projections/eqearth.rst new file mode 100644 index 00000000..4c9aa661 --- /dev/null +++ b/assets/proj4/projections/eqearth.rst @@ -0,0 +1,68 @@ +.. _eqearth: + +******************************************************************************** +Equal Earth +******************************************************************************** + +.. versionadded:: 5.2.0 + ++---------------------+-----------------------------------------------------------+ +| **Classification** | Pseudo cylindrical | ++---------------------+-----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal projection | ++---------------------+-----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+-----------------------------------------------------------+ +| **Alias** | eqearth | ++---------------------+-----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+-----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+-----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+-----------------------------------------------------------+ + + + +.. figure:: ./images/eqearth.png + :width: 500 px + :align: center + :alt: Equal Earth + + proj-string: ``+proj=eqearth`` + + +The Equal Earth projection is intended for making world maps. Equal Earth is a +projection inspired by the Robinson projection, but unlike +the Robinson projection retains the relative size of areas. The projection +was designed in 2018 by Bojan Savric, Tom Patterson and Bernhard Jenny :cite:`Savric2018`. + + +Usage +############################################################################### + +The Equal Earth projection has no special options. Here is +an example of an forward projection on a sphere with a radius of 1 m:: + + $ echo 122 47 | proj +proj=eqearth +R=1 + 1.55 0.89 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. Bojan Savric, Tom Patterson & Bernhard Jenny (2018). `The Equal Earth map projection `_, International Journal of Geographical Information Science diff --git a/assets/proj4/projections/euler.rst b/assets/proj4/projections/euler.rst new file mode 100644 index 00000000..bac89855 --- /dev/null +++ b/assets/proj4/projections/euler.rst @@ -0,0 +1,51 @@ +.. _euler: + +******************************************************************************** +Euler +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | euler | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/euler.png + :width: 500 px + :align: center + :alt: Euler + + proj-string: ``+proj=euler +lat_1=67 +lat_2=75`` + + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/fahey.rst b/assets/proj4/projections/fahey.rst new file mode 100644 index 00000000..39a7b1be --- /dev/null +++ b/assets/proj4/projections/fahey.rst @@ -0,0 +1,42 @@ +.. _fahey: + +******************************************************************************** +Fahey +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | fahey | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/fahey.png + :width: 500 px + :align: center + :alt: Fahey + + proj-string: ``+proj=fahey`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Fahey projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/fouc.rst b/assets/proj4/projections/fouc.rst new file mode 100644 index 00000000..3bcb3e7d --- /dev/null +++ b/assets/proj4/projections/fouc.rst @@ -0,0 +1,42 @@ +.. _fouc: + +******************************************************************************** +Foucaut +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | fouc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/fouc.png + :width: 500 px + :align: center + :alt: Foucaut + + proj-string: ``+proj=fouc`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Foucaut projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/fouc_s.rst b/assets/proj4/projections/fouc_s.rst new file mode 100644 index 00000000..1938b637 --- /dev/null +++ b/assets/proj4/projections/fouc_s.rst @@ -0,0 +1,63 @@ +.. _fouc_s: + +******************************************************************************** +Foucaut Sinusoidal +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | fouc_s | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/fouc_s.png + :width: 500 px + :align: center + :alt: Foucaut Sinusoidal + + proj-string: ``+proj=fouc_s`` + + +The `y`-axis is based upon a weighted mean of the cylindrical equal-area and +the sinusoidal projections. Parameter :math:`n=n` is the weighting factor where +:math:`0 <= n <= 1`. + +.. math:: + + x &= \lambda \cos \phi / (n + (1 - n) \ cos \phi) + + y &= n \phi + (1 - n) \sin \phi + +For the inverse, the Newton-Raphson method can be used to determine +:math:`\phi` from the equation for :math:`y` above. As :math:`n \rightarrow 0` and +:math:`\phi \rightarrow \pi/2`, convergence is slow but for :math:`n = 0`, :math:`\phi = +\sin^1y` + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Foucaut Sinusoidal projection. + +.. option:: +n= + + Weighting factor. Value should be in the interval 0-1. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gall.rst b/assets/proj4/projections/gall.rst new file mode 100644 index 00000000..6ef99d0f --- /dev/null +++ b/assets/proj4/projections/gall.rst @@ -0,0 +1,111 @@ +.. _gall: + +******************************************************************************** +Gall (Gall Stereographic) +******************************************************************************** + +The Gall stereographic projection, presented by James Gall in 1855, is a cylindrical projection. +It is neither equal-area nor conformal but instead tries to balance the distortion inherent in any projection. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Transverse and oblique cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, Spherical | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | gall | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gall.png + :width: 500 px + :align: center + :alt: Gall (Gall Stereographic) + + proj-string: ``+proj=gall`` + +Usage +##### + +The need for a world map which avoids some of the scale exaggeration of the Mercator projection has led to some commonly used cylindrical modifications, as well as to other modifications which are not cylindrical. +The earliest common cylindrical example was developed by James Gall of Edinburgh about 1855 (Gall, 1885, p. 119-123). +His meridians are equally spaced, but the parallels are spaced at increasing intervals away from the Equator. +The parallels of latitude are actually projected onto a cylinder wrapped about the sphere, but cutting it at lats. 45° N. and S., the point of perspective being a point on the Equator opposite the meridian being projected. +It is used in several British atlases, but seldom in the United States. +The Gall projection is neither conformal nor equal-area, but has a blend of various features. +Unlike the Mercator, the Gall shows the poles as lines running across the top and bottom of the map. + +.. note:: + + The Gall projection must not be confused with the Gall-Peters one, the later + being a specialization of :ref:`cea`. + +Example using Gall Stereographic :: + + $ echo 9 51 | proj +proj=gall + 708432.90 5193386.36 + +Example using Gall Stereographic (Central meridian 90°W) :: + + $ echo 9 51 | proj +proj=gall +lon_0=90w + 7792761.91 5193386.36 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + + +Mathematical definition +####################### + +The formulas describing the Gall Stereographic are all taken from :cite:`Snyder1993`. + +Spherical form +************** + +Forward projection +================== + +.. math:: + + x = \frac{\lambda}{\sqrt{2}} + +.. math:: + + y = (1+\frac{\sqrt{2}}{2}) \tan(\phi/2) + +Inverse projection +================== + +.. math:: + + \phi = 2 \arctan( \frac{y}{1+\frac{\sqrt{2}}{2}} ) + +.. math:: + + \lambda = \sqrt{2} x + + +Further reading +############### + +#. `Wikipedia `_ +#. `Cartographic Projection Procedures for the UNIX Environment-A User's Manual `_ diff --git a/assets/proj4/projections/geos.rst b/assets/proj4/projections/geos.rst new file mode 100644 index 00000000..9b8bf21b --- /dev/null +++ b/assets/proj4/projections/geos.rst @@ -0,0 +1,103 @@ +.. _geos: + +******************************************************************************** +Geostationary Satellite View +******************************************************************************** + +The geos projection pictures how a geostationary satellite scans the earth at regular +scanning angle intervals. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | geos | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/geos.png + :width: 500 px + :align: center + :alt: Geostationary Satellite View + + proj-string: ``+proj=geos +h=35785831.0 +lon_0=-60 +sweep=y`` + +Usage +############################################################################### + +In order to project using the geos projection you can do the following:: + + proj +proj=geos +h=35785831.0 + +The required argument ``h`` is the viewing point (satellite position) height above +the earth. + +The projection coordinate relate to the scanning angle by the following simple +relation:: + + scanning_angle (radians) = projection_coordinate / h + + +Note on sweep angle +------------------------------------------------------------------------------- + +The viewing instrument on-board geostationary satellites described by this +projection have a two-axis gimbal viewing geometry. This means that the different +scanning positions are obtained by rotating the gimbal along a N/S axis (or ``y``) +and a E/W axis (or ``x``). + +.. image:: ../../..//images/geos_sweep.png + :width: 500 px + :align: center + :alt: Gimbal geometry + +In the image above, the outer-gimbal axis, or sweep-angle axis, is the N/S axis (``y``) +while the inner-gimbal axis, or fixed-angle axis, is the E/W axis (``x``). + +This example represents the scanning geometry of the Meteosat series satellite. +However, the GOES satellite series use the opposite scanning geometry, with the +E/W axis (``x``) as the sweep-angle axis, and the N/S (``y``) as the fixed-angle axis. + +The sweep argument is used to tell PROJ which on which axis the outer-gimbal +is rotating. The possible values are x or y, y being the default. Thus, the +scanning geometry of the Meteosat series satellite should take sweep as y, and +GOES should take sweep as x. + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. include:: ../options/h.rst + + +Optional +------------------------------------------------------------------------------- + +.. option:: +sweep= + + Sweep angle axis of the viewing instrument. Valid options are *"x"* and *"y*". + + *Defaults to "y".* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + + diff --git a/assets/proj4/projections/gins8.rst b/assets/proj4/projections/gins8.rst new file mode 100644 index 00000000..07c26912 --- /dev/null +++ b/assets/proj4/projections/gins8.rst @@ -0,0 +1,42 @@ +.. _gins8: + +******************************************************************************** +Ginsburg VIII (TsNIIGAiK) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | gins8 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gins8.png + :width: 500 px + :align: center + :alt: Ginsburg VIII (TsNIIGAiK) + + proj-string: ``+proj=gins8`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Ginsburg VIII projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gn_sinu.rst b/assets/proj4/projections/gn_sinu.rst new file mode 100644 index 00000000..a42e1d14 --- /dev/null +++ b/assets/proj4/projections/gn_sinu.rst @@ -0,0 +1,43 @@ +.. _gn_sinu: + +******************************************************************************** +General Sinusoidal Series +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | gn_sinu | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gn_sinu.png + :width: 500 px + :align: center + :alt: General Sinusoidal Series + + proj-string: ``+proj=gn_sinu +m=2 +n=3`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the General Sinusoidal Series + projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gnom.rst b/assets/proj4/projections/gnom.rst new file mode 100644 index 00000000..06a9edbf --- /dev/null +++ b/assets/proj4/projections/gnom.rst @@ -0,0 +1,55 @@ +.. _gnom: + +******************************************************************************** +Gnomonic +******************************************************************************** + +For a sphere, the gnomonic projection is a projection from the center of +the sphere onto a plane tangent to the center point of the projection. +This projects great circles to straight lines. For an ellipsoid, it is +the limit of a doubly azimuthal projection, a projection where the +azimuths from 2 points are preserved, as the two points merge into the +center point. In this case, geodesics project to approximately straight +lines (these are exactly straight if the geodesic includes the center +point). For details, see Section 8 of :cite:`Karney2013`. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Within a quarter circumference of the center point | ++---------------------+----------------------------------------------------------+ +| **Alias** | gnom | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gnom.png + :width: 500 px + :align: center + :alt: Gnomonic + + proj-string: ``+proj=gnom +lat_0=90 +lon_0=-50 +R=6.4e6`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Gnomonic projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst diff --git a/assets/proj4/projections/goode.rst b/assets/proj4/projections/goode.rst new file mode 100644 index 00000000..55f0f370 --- /dev/null +++ b/assets/proj4/projections/goode.rst @@ -0,0 +1,42 @@ +.. _goode: + +******************************************************************************** +Goode Homolosine +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | goode | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/goode.png + :width: 500 px + :align: center + :alt: Goode Homolosine + + proj-string: ``+proj=goode`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Goode Homolosine projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gs48.rst b/assets/proj4/projections/gs48.rst new file mode 100644 index 00000000..b8088fb4 --- /dev/null +++ b/assets/proj4/projections/gs48.rst @@ -0,0 +1,40 @@ +.. _gs48: + +******************************************************************************** +Modified Stereographic of 48 U.S. +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | The lower 48 states of the U.S. | ++---------------------+----------------------------------------------------------+ +| **Alias** | gs48 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gs48.png + :width: 500 px + :align: center + :alt: Modified Stereographic of 48 U.S. + + proj-string: ``+proj=gs48`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gs50.rst b/assets/proj4/projections/gs50.rst new file mode 100644 index 00000000..5378c25a --- /dev/null +++ b/assets/proj4/projections/gs50.rst @@ -0,0 +1,40 @@ +.. _gs50: + +******************************************************************************** +Modified Stereographic of the 50 U.S. states +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | All 50 states of the U.S. | ++---------------------+----------------------------------------------------------+ +| **Alias** | gs50 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gs50.png + :width: 500 px + :align: center + :alt: Modified Stereographic of the 50 U.S. states + + proj-string: ``+proj=gs50`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/gstmerc.rst b/assets/proj4/projections/gstmerc.rst new file mode 100644 index 00000000..33c68eff --- /dev/null +++ b/assets/proj4/projections/gstmerc.rst @@ -0,0 +1,48 @@ +.. _gstmerc: + +******************************************************************************** +Gauss-Schreiber Transverse Mercator (aka Gauss-Laborde Reunion) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | gstmerc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/gstmerc.png + :width: 500 px + :align: center + :alt: Gauss-Schreiber Transverse Mercator (aka Gauss-Laborde Reunion) + + proj-string: ``+proj=gstmerc`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/k_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/guyou.rst b/assets/proj4/projections/guyou.rst new file mode 100644 index 00000000..89d455be --- /dev/null +++ b/assets/proj4/projections/guyou.rst @@ -0,0 +1,42 @@ +.. _guyou: + +******************************************************************************** +Guyou +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | guyou | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/guyou.png + :width: 500 px + :align: center + :alt: Guyou + + proj-string: ``+proj=guyou`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/hammer.rst b/assets/proj4/projections/hammer.rst new file mode 100644 index 00000000..c3551d0c --- /dev/null +++ b/assets/proj4/projections/hammer.rst @@ -0,0 +1,55 @@ +.. _hammer: + +******************************************************************************** +Hammer & Eckert-Greifendorff +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | hammer | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/hammer.png + :width: 500 px + :align: center + :alt: Hammer & Eckert-Greifendorff + + proj-string: ``+proj=hammer`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +W= + + Set to 0.5 for the Hammer projection and 0.25 for the Eckert-Greifendorff + projection. :option:`+W` has to be larger than zero. + + *Defaults to 0.5.* + +.. option:: +M= + + :option:`+M` has to be larger than zero. + + *Defaults to 1.0.* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/hatano.rst b/assets/proj4/projections/hatano.rst new file mode 100644 index 00000000..35bc32c3 --- /dev/null +++ b/assets/proj4/projections/hatano.rst @@ -0,0 +1,84 @@ +.. _hatano: + +******************************************************************************** +Hatano Asymmetrical Equal Area +******************************************************************************** + + + + ++---------------------+----------------------------------------------------------+ +| **Classification** | :term:`Pseudocylindrical Projection` | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | hatano | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + + +.. figure:: ./images/hatano.png + :width: 500 px + :align: center + :alt: Hatano Asymmetrical Equal Area + + proj-string: ``+proj=hatano`` + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + + +Mathematical Definition +-------------------------------------------------------------------------------- + +Forward +................................................................................ + +.. math:: + + x &= 0.85\lambda \cos \theta + + y &= C_y \sin \theta + + P(\theta) &= 2\theta + \sin 2\theta - C_p \sin \phi + + P'(\theta) &= 2(1 + \cos 2\theta) + + \theta_0 &= 2\phi + + +==================================== ================== =================== +Condition :math:`C_y` :math:`C_p` +==================================== ================== =================== +For :math:`\phi > 0` 1.75859 2.67595 +For :math:`\phi < 0` 1.93052 2.43763 +==================================== ================== =================== + +For :math:`\phi = 0`, :math:`y \leftarrow 0`, and :math:`x \leftarrow 0.85\lambda`. + +Further reading +-------------------------------------------------------------------------------- + +#. `Compare Map Projections `__ +#. `Mathworks `__ + + + diff --git a/assets/proj4/projections/healpix.rst b/assets/proj4/projections/healpix.rst new file mode 100644 index 00000000..ec00f90a --- /dev/null +++ b/assets/proj4/projections/healpix.rst @@ -0,0 +1,77 @@ +.. _healpix: + +******************************************************************************** +HEALPix +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | healpix | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. image:: ../../../images/healpix.png + :scale: 75% + :alt: HEALPix + +The HEALPix projection is area preserving and can be used with a spherical and +ellipsoidal model. It was initially developed for mapping cosmic background +microwave radiation. The image below is the graphical representation of the +mapping and consists of eight isomorphic triangular interrupted map graticules. +The north and south contains four in which straight meridians converge polewards +to a point and unequally spaced horizontal parallels. HEALPix provides a mapping +in which points of equal latitude and equally spaced longitude are mapped to points +of equal latitude and equally spaced longitude with the module of the polar +interruptions. + + +Usage +############################################################################### + +To run a forward HEALPix projection on a unit sphere model, use the following command:: + + proj +proj=healpix +a=1 -E <`_ +#. `Wikipedia `_ diff --git a/assets/proj4/projections/igh.rst b/assets/proj4/projections/igh.rst new file mode 100644 index 00000000..b08f013d --- /dev/null +++ b/assets/proj4/projections/igh.rst @@ -0,0 +1,52 @@ +.. _igh: + +******************************************************************************** +Interrupted Goode Homolosine +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | igh | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/igh.png + :width: 500 px + :align: center + :alt: Interrupted Goode Homolosine + + proj-string: ``+proj=igh`` + + +The Interrupted Goode Homolosine projection is an equal-area composite +projection intended for making world maps. Low latitudes are comprised of six +separate Sinusoidal projection regions, and high latitudes are comprised of +six separate Mollweide (``homolographic``) projections. The transition latitude +is at 40d 44' 11.8", where the Sinusoidal and Mollweide scales are equal. The +lobes in this projection are chosen to emphasize the land area of the Earth. This +projection was first published in 1925 by J. P. Goode :cite:`Goode1925`. + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/igh_o.rst b/assets/proj4/projections/igh_o.rst new file mode 100644 index 00000000..b5d4a300 --- /dev/null +++ b/assets/proj4/projections/igh_o.rst @@ -0,0 +1,54 @@ +.. _igh_o: + +******************************************************************************** +Interrupted Goode Homolosine (Oceanic View) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | igh_o | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/igh_o.png + :width: 500 px + :align: center + :alt: Interrupted Goode Homolosine + + proj-string: ``+proj=igh_o +lon_0=-160`` + + +The Interrupted Goode Homolosine (Oceanic View) projection is an equal-area +composite projection intended for making maps of the Earth's oceans. Low +latitudes are comprised of six separate Sinusoidal projection regions, and high +latitudes are comprised of six separate Mollweide (``homolographic``) +projections. The transition latitude is at 40d 44' 11.8", where the Sinusoidal +and Mollweide scales are equal. The lobes in this projection are chosen to +emphasize the ocean area of the Earth when used with a central longitude +of -160 degrees. This projection was first published in 1925 by J. P. Goode +:cite:`Goode1925`. + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. A value of +lon_0=-160 is recommended. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/images/adams_hemi.png b/assets/proj4/projections/images/adams_hemi.png new file mode 100644 index 00000000..83df50d6 Binary files /dev/null and b/assets/proj4/projections/images/adams_hemi.png differ diff --git a/assets/proj4/projections/images/adams_ws1.png b/assets/proj4/projections/images/adams_ws1.png new file mode 100644 index 00000000..5ec2917b Binary files /dev/null and b/assets/proj4/projections/images/adams_ws1.png differ diff --git a/assets/proj4/projections/images/adams_ws2.png b/assets/proj4/projections/images/adams_ws2.png new file mode 100644 index 00000000..b133c084 Binary files /dev/null and b/assets/proj4/projections/images/adams_ws2.png differ diff --git a/assets/proj4/projections/images/aea.png b/assets/proj4/projections/images/aea.png new file mode 100644 index 00000000..7fed55e1 Binary files /dev/null and b/assets/proj4/projections/images/aea.png differ diff --git a/assets/proj4/projections/images/aeqd.png b/assets/proj4/projections/images/aeqd.png new file mode 100644 index 00000000..7fd53986 Binary files /dev/null and b/assets/proj4/projections/images/aeqd.png differ diff --git a/assets/proj4/projections/images/airy.png b/assets/proj4/projections/images/airy.png new file mode 100644 index 00000000..c4f6313f Binary files /dev/null and b/assets/proj4/projections/images/airy.png differ diff --git a/assets/proj4/projections/images/aitoff.png b/assets/proj4/projections/images/aitoff.png new file mode 100644 index 00000000..61a7004c Binary files /dev/null and b/assets/proj4/projections/images/aitoff.png differ diff --git a/assets/proj4/projections/images/alsk.png b/assets/proj4/projections/images/alsk.png new file mode 100644 index 00000000..d4b490a9 Binary files /dev/null and b/assets/proj4/projections/images/alsk.png differ diff --git a/assets/proj4/projections/images/apian.png b/assets/proj4/projections/images/apian.png new file mode 100644 index 00000000..3910659f Binary files /dev/null and b/assets/proj4/projections/images/apian.png differ diff --git a/assets/proj4/projections/images/august.png b/assets/proj4/projections/images/august.png new file mode 100644 index 00000000..fb5ed211 Binary files /dev/null and b/assets/proj4/projections/images/august.png differ diff --git a/assets/proj4/projections/images/bacon.png b/assets/proj4/projections/images/bacon.png new file mode 100644 index 00000000..c7449e21 Binary files /dev/null and b/assets/proj4/projections/images/bacon.png differ diff --git a/assets/proj4/projections/images/bertin1953.png b/assets/proj4/projections/images/bertin1953.png new file mode 100644 index 00000000..e8a8634d Binary files /dev/null and b/assets/proj4/projections/images/bertin1953.png differ diff --git a/assets/proj4/projections/images/bipc.png b/assets/proj4/projections/images/bipc.png new file mode 100644 index 00000000..5a522220 Binary files /dev/null and b/assets/proj4/projections/images/bipc.png differ diff --git a/assets/proj4/projections/images/boggs.png b/assets/proj4/projections/images/boggs.png new file mode 100644 index 00000000..b254e319 Binary files /dev/null and b/assets/proj4/projections/images/boggs.png differ diff --git a/assets/proj4/projections/images/bonne.png b/assets/proj4/projections/images/bonne.png new file mode 100644 index 00000000..575e4798 Binary files /dev/null and b/assets/proj4/projections/images/bonne.png differ diff --git a/assets/proj4/projections/images/calcofi.png b/assets/proj4/projections/images/calcofi.png new file mode 100644 index 00000000..2fd405ac Binary files /dev/null and b/assets/proj4/projections/images/calcofi.png differ diff --git a/assets/proj4/projections/images/cass.png b/assets/proj4/projections/images/cass.png new file mode 100644 index 00000000..30c1cd74 Binary files /dev/null and b/assets/proj4/projections/images/cass.png differ diff --git a/assets/proj4/projections/images/cc.png b/assets/proj4/projections/images/cc.png new file mode 100644 index 00000000..6ff6cf21 Binary files /dev/null and b/assets/proj4/projections/images/cc.png differ diff --git a/assets/proj4/projections/images/ccon.png b/assets/proj4/projections/images/ccon.png new file mode 100644 index 00000000..2d604821 Binary files /dev/null and b/assets/proj4/projections/images/ccon.png differ diff --git a/assets/proj4/projections/images/cea.png b/assets/proj4/projections/images/cea.png new file mode 100644 index 00000000..ca5d4c96 Binary files /dev/null and b/assets/proj4/projections/images/cea.png differ diff --git a/assets/proj4/projections/images/chamb.png b/assets/proj4/projections/images/chamb.png new file mode 100644 index 00000000..c37444f5 Binary files /dev/null and b/assets/proj4/projections/images/chamb.png differ diff --git a/assets/proj4/projections/images/collg.png b/assets/proj4/projections/images/collg.png new file mode 100644 index 00000000..159e3cf1 Binary files /dev/null and b/assets/proj4/projections/images/collg.png differ diff --git a/assets/proj4/projections/images/comill.png b/assets/proj4/projections/images/comill.png new file mode 100644 index 00000000..ba818799 Binary files /dev/null and b/assets/proj4/projections/images/comill.png differ diff --git a/assets/proj4/projections/images/crast.png b/assets/proj4/projections/images/crast.png new file mode 100644 index 00000000..94abbd6a Binary files /dev/null and b/assets/proj4/projections/images/crast.png differ diff --git a/assets/proj4/projections/images/denoy.png b/assets/proj4/projections/images/denoy.png new file mode 100644 index 00000000..3c7ddb47 Binary files /dev/null and b/assets/proj4/projections/images/denoy.png differ diff --git a/assets/proj4/projections/images/eck1.png b/assets/proj4/projections/images/eck1.png new file mode 100644 index 00000000..b4cb4c6d Binary files /dev/null and b/assets/proj4/projections/images/eck1.png differ diff --git a/assets/proj4/projections/images/eck2.png b/assets/proj4/projections/images/eck2.png new file mode 100644 index 00000000..9c5a3680 Binary files /dev/null and b/assets/proj4/projections/images/eck2.png differ diff --git a/assets/proj4/projections/images/eck3.png b/assets/proj4/projections/images/eck3.png new file mode 100644 index 00000000..930aaabc Binary files /dev/null and b/assets/proj4/projections/images/eck3.png differ diff --git a/assets/proj4/projections/images/eck4.png b/assets/proj4/projections/images/eck4.png new file mode 100644 index 00000000..62fc32cc Binary files /dev/null and b/assets/proj4/projections/images/eck4.png differ diff --git a/assets/proj4/projections/images/eck5.png b/assets/proj4/projections/images/eck5.png new file mode 100644 index 00000000..73bdf4bc Binary files /dev/null and b/assets/proj4/projections/images/eck5.png differ diff --git a/assets/proj4/projections/images/eck6.png b/assets/proj4/projections/images/eck6.png new file mode 100644 index 00000000..7e3e7608 Binary files /dev/null and b/assets/proj4/projections/images/eck6.png differ diff --git a/assets/proj4/projections/images/eqc.png b/assets/proj4/projections/images/eqc.png new file mode 100644 index 00000000..f2e24ccf Binary files /dev/null and b/assets/proj4/projections/images/eqc.png differ diff --git a/assets/proj4/projections/images/eqdc.png b/assets/proj4/projections/images/eqdc.png new file mode 100644 index 00000000..44d271f2 Binary files /dev/null and b/assets/proj4/projections/images/eqdc.png differ diff --git a/assets/proj4/projections/images/eqearth.png b/assets/proj4/projections/images/eqearth.png new file mode 100644 index 00000000..f3cdbb06 Binary files /dev/null and b/assets/proj4/projections/images/eqearth.png differ diff --git a/assets/proj4/projections/images/euler.png b/assets/proj4/projections/images/euler.png new file mode 100644 index 00000000..712fc154 Binary files /dev/null and b/assets/proj4/projections/images/euler.png differ diff --git a/assets/proj4/projections/images/fahey.png b/assets/proj4/projections/images/fahey.png new file mode 100644 index 00000000..16f63d07 Binary files /dev/null and b/assets/proj4/projections/images/fahey.png differ diff --git a/assets/proj4/projections/images/fouc.png b/assets/proj4/projections/images/fouc.png new file mode 100644 index 00000000..1b0114c7 Binary files /dev/null and b/assets/proj4/projections/images/fouc.png differ diff --git a/assets/proj4/projections/images/fouc_s.png b/assets/proj4/projections/images/fouc_s.png new file mode 100644 index 00000000..a1094f36 Binary files /dev/null and b/assets/proj4/projections/images/fouc_s.png differ diff --git a/assets/proj4/projections/images/gall.png b/assets/proj4/projections/images/gall.png new file mode 100644 index 00000000..53e7e491 Binary files /dev/null and b/assets/proj4/projections/images/gall.png differ diff --git a/assets/proj4/projections/images/geos.png b/assets/proj4/projections/images/geos.png new file mode 100644 index 00000000..e2cfcb9d Binary files /dev/null and b/assets/proj4/projections/images/geos.png differ diff --git a/assets/proj4/projections/images/gins8.png b/assets/proj4/projections/images/gins8.png new file mode 100644 index 00000000..fbf0988a Binary files /dev/null and b/assets/proj4/projections/images/gins8.png differ diff --git a/assets/proj4/projections/images/gn_sinu.png b/assets/proj4/projections/images/gn_sinu.png new file mode 100644 index 00000000..ce9cad90 Binary files /dev/null and b/assets/proj4/projections/images/gn_sinu.png differ diff --git a/assets/proj4/projections/images/gnom.png b/assets/proj4/projections/images/gnom.png new file mode 100644 index 00000000..76daa636 Binary files /dev/null and b/assets/proj4/projections/images/gnom.png differ diff --git a/assets/proj4/projections/images/goode.png b/assets/proj4/projections/images/goode.png new file mode 100644 index 00000000..2424980e Binary files /dev/null and b/assets/proj4/projections/images/goode.png differ diff --git a/assets/proj4/projections/images/grieger_triptychial.png b/assets/proj4/projections/images/grieger_triptychial.png new file mode 100644 index 00000000..ee7874eb Binary files /dev/null and b/assets/proj4/projections/images/grieger_triptychial.png differ diff --git a/assets/proj4/projections/images/gs48.png b/assets/proj4/projections/images/gs48.png new file mode 100644 index 00000000..ccb0044e Binary files /dev/null and b/assets/proj4/projections/images/gs48.png differ diff --git a/assets/proj4/projections/images/gs50.png b/assets/proj4/projections/images/gs50.png new file mode 100644 index 00000000..8d295cac Binary files /dev/null and b/assets/proj4/projections/images/gs50.png differ diff --git a/assets/proj4/projections/images/gstmerc.png b/assets/proj4/projections/images/gstmerc.png new file mode 100644 index 00000000..696a3693 Binary files /dev/null and b/assets/proj4/projections/images/gstmerc.png differ diff --git a/assets/proj4/projections/images/guyou.png b/assets/proj4/projections/images/guyou.png new file mode 100644 index 00000000..b2fa98b5 Binary files /dev/null and b/assets/proj4/projections/images/guyou.png differ diff --git a/assets/proj4/projections/images/hammer.png b/assets/proj4/projections/images/hammer.png new file mode 100644 index 00000000..818b8409 Binary files /dev/null and b/assets/proj4/projections/images/hammer.png differ diff --git a/assets/proj4/projections/images/hatano.png b/assets/proj4/projections/images/hatano.png new file mode 100644 index 00000000..88f432da Binary files /dev/null and b/assets/proj4/projections/images/hatano.png differ diff --git a/assets/proj4/projections/images/healpix.png b/assets/proj4/projections/images/healpix.png new file mode 100644 index 00000000..c58a155c Binary files /dev/null and b/assets/proj4/projections/images/healpix.png differ diff --git a/assets/proj4/projections/images/igh.png b/assets/proj4/projections/images/igh.png new file mode 100644 index 00000000..9c41429c Binary files /dev/null and b/assets/proj4/projections/images/igh.png differ diff --git a/assets/proj4/projections/images/igh_o.png b/assets/proj4/projections/images/igh_o.png new file mode 100644 index 00000000..9683832b Binary files /dev/null and b/assets/proj4/projections/images/igh_o.png differ diff --git a/assets/proj4/projections/images/imoll.png b/assets/proj4/projections/images/imoll.png new file mode 100644 index 00000000..bfa806d2 Binary files /dev/null and b/assets/proj4/projections/images/imoll.png differ diff --git a/assets/proj4/projections/images/imoll_o.png b/assets/proj4/projections/images/imoll_o.png new file mode 100644 index 00000000..7283ea01 Binary files /dev/null and b/assets/proj4/projections/images/imoll_o.png differ diff --git a/assets/proj4/projections/images/imw_p.png b/assets/proj4/projections/images/imw_p.png new file mode 100644 index 00000000..ae7bd968 Binary files /dev/null and b/assets/proj4/projections/images/imw_p.png differ diff --git a/assets/proj4/projections/images/isea.png b/assets/proj4/projections/images/isea.png new file mode 100644 index 00000000..f99c3d01 Binary files /dev/null and b/assets/proj4/projections/images/isea.png differ diff --git a/assets/proj4/projections/images/kav5.png b/assets/proj4/projections/images/kav5.png new file mode 100644 index 00000000..9c56fe5f Binary files /dev/null and b/assets/proj4/projections/images/kav5.png differ diff --git a/assets/proj4/projections/images/kav7.png b/assets/proj4/projections/images/kav7.png new file mode 100644 index 00000000..744edb1f Binary files /dev/null and b/assets/proj4/projections/images/kav7.png differ diff --git a/assets/proj4/projections/images/krovak.png b/assets/proj4/projections/images/krovak.png new file mode 100644 index 00000000..258afe86 Binary files /dev/null and b/assets/proj4/projections/images/krovak.png differ diff --git a/assets/proj4/projections/images/labrd.png b/assets/proj4/projections/images/labrd.png new file mode 100644 index 00000000..d2aab3ee Binary files /dev/null and b/assets/proj4/projections/images/labrd.png differ diff --git a/assets/proj4/projections/images/laea.png b/assets/proj4/projections/images/laea.png new file mode 100644 index 00000000..bd0a8ffe Binary files /dev/null and b/assets/proj4/projections/images/laea.png differ diff --git a/assets/proj4/projections/images/lagrng.png b/assets/proj4/projections/images/lagrng.png new file mode 100644 index 00000000..ab1413b9 Binary files /dev/null and b/assets/proj4/projections/images/lagrng.png differ diff --git a/assets/proj4/projections/images/larr.png b/assets/proj4/projections/images/larr.png new file mode 100644 index 00000000..ce5083a0 Binary files /dev/null and b/assets/proj4/projections/images/larr.png differ diff --git a/assets/proj4/projections/images/lask.png b/assets/proj4/projections/images/lask.png new file mode 100644 index 00000000..3ccd2698 Binary files /dev/null and b/assets/proj4/projections/images/lask.png differ diff --git a/assets/proj4/projections/images/lcc.png b/assets/proj4/projections/images/lcc.png new file mode 100644 index 00000000..f86ec26a Binary files /dev/null and b/assets/proj4/projections/images/lcc.png differ diff --git a/assets/proj4/projections/images/lcca.png b/assets/proj4/projections/images/lcca.png new file mode 100644 index 00000000..412c9bde Binary files /dev/null and b/assets/proj4/projections/images/lcca.png differ diff --git a/assets/proj4/projections/images/leac.png b/assets/proj4/projections/images/leac.png new file mode 100644 index 00000000..27ad0074 Binary files /dev/null and b/assets/proj4/projections/images/leac.png differ diff --git a/assets/proj4/projections/images/lee_os.png b/assets/proj4/projections/images/lee_os.png new file mode 100644 index 00000000..a6b00ad0 Binary files /dev/null and b/assets/proj4/projections/images/lee_os.png differ diff --git a/assets/proj4/projections/images/loxim.png b/assets/proj4/projections/images/loxim.png new file mode 100644 index 00000000..9912a278 Binary files /dev/null and b/assets/proj4/projections/images/loxim.png differ diff --git a/assets/proj4/projections/images/lsat.png b/assets/proj4/projections/images/lsat.png new file mode 100644 index 00000000..c1f58642 Binary files /dev/null and b/assets/proj4/projections/images/lsat.png differ diff --git a/assets/proj4/projections/images/mbt_fps.png b/assets/proj4/projections/images/mbt_fps.png new file mode 100644 index 00000000..d6e23611 Binary files /dev/null and b/assets/proj4/projections/images/mbt_fps.png differ diff --git a/assets/proj4/projections/images/mbt_s.png b/assets/proj4/projections/images/mbt_s.png new file mode 100644 index 00000000..e791af54 Binary files /dev/null and b/assets/proj4/projections/images/mbt_s.png differ diff --git a/assets/proj4/projections/images/mbtfpp.png b/assets/proj4/projections/images/mbtfpp.png new file mode 100644 index 00000000..d6e3da4f Binary files /dev/null and b/assets/proj4/projections/images/mbtfpp.png differ diff --git a/assets/proj4/projections/images/mbtfpq.png b/assets/proj4/projections/images/mbtfpq.png new file mode 100644 index 00000000..34df20d9 Binary files /dev/null and b/assets/proj4/projections/images/mbtfpq.png differ diff --git a/assets/proj4/projections/images/mbtfps.png b/assets/proj4/projections/images/mbtfps.png new file mode 100644 index 00000000..249b8455 Binary files /dev/null and b/assets/proj4/projections/images/mbtfps.png differ diff --git a/assets/proj4/projections/images/merc.png b/assets/proj4/projections/images/merc.png new file mode 100644 index 00000000..1416ab48 Binary files /dev/null and b/assets/proj4/projections/images/merc.png differ diff --git a/assets/proj4/projections/images/mil_os.png b/assets/proj4/projections/images/mil_os.png new file mode 100644 index 00000000..9b6c5196 Binary files /dev/null and b/assets/proj4/projections/images/mil_os.png differ diff --git a/assets/proj4/projections/images/mill.png b/assets/proj4/projections/images/mill.png new file mode 100644 index 00000000..362dbeba Binary files /dev/null and b/assets/proj4/projections/images/mill.png differ diff --git a/assets/proj4/projections/images/misrsom.png b/assets/proj4/projections/images/misrsom.png new file mode 100644 index 00000000..5c197ae4 Binary files /dev/null and b/assets/proj4/projections/images/misrsom.png differ diff --git a/assets/proj4/projections/images/moll.png b/assets/proj4/projections/images/moll.png new file mode 100644 index 00000000..c30c5c8b Binary files /dev/null and b/assets/proj4/projections/images/moll.png differ diff --git a/assets/proj4/projections/images/murd1.png b/assets/proj4/projections/images/murd1.png new file mode 100644 index 00000000..a3165390 Binary files /dev/null and b/assets/proj4/projections/images/murd1.png differ diff --git a/assets/proj4/projections/images/murd2.png b/assets/proj4/projections/images/murd2.png new file mode 100644 index 00000000..d0737e2a Binary files /dev/null and b/assets/proj4/projections/images/murd2.png differ diff --git a/assets/proj4/projections/images/murd3.png b/assets/proj4/projections/images/murd3.png new file mode 100644 index 00000000..79b89df9 Binary files /dev/null and b/assets/proj4/projections/images/murd3.png differ diff --git a/assets/proj4/projections/images/natearth.png b/assets/proj4/projections/images/natearth.png new file mode 100644 index 00000000..7d15eb81 Binary files /dev/null and b/assets/proj4/projections/images/natearth.png differ diff --git a/assets/proj4/projections/images/natearth2.png b/assets/proj4/projections/images/natearth2.png new file mode 100644 index 00000000..6d6f9a13 Binary files /dev/null and b/assets/proj4/projections/images/natearth2.png differ diff --git a/assets/proj4/projections/images/nell.png b/assets/proj4/projections/images/nell.png new file mode 100644 index 00000000..75b91872 Binary files /dev/null and b/assets/proj4/projections/images/nell.png differ diff --git a/assets/proj4/projections/images/nell_h.png b/assets/proj4/projections/images/nell_h.png new file mode 100644 index 00000000..8917ada1 Binary files /dev/null and b/assets/proj4/projections/images/nell_h.png differ diff --git a/assets/proj4/projections/images/nicol.png b/assets/proj4/projections/images/nicol.png new file mode 100644 index 00000000..6695cc7a Binary files /dev/null and b/assets/proj4/projections/images/nicol.png differ diff --git a/assets/proj4/projections/images/nsper.png b/assets/proj4/projections/images/nsper.png new file mode 100644 index 00000000..c34e45fd Binary files /dev/null and b/assets/proj4/projections/images/nsper.png differ diff --git a/assets/proj4/projections/images/nzmg.png b/assets/proj4/projections/images/nzmg.png new file mode 100644 index 00000000..92b30e5b Binary files /dev/null and b/assets/proj4/projections/images/nzmg.png differ diff --git a/assets/proj4/projections/images/ob_tran.png b/assets/proj4/projections/images/ob_tran.png new file mode 100644 index 00000000..dfa1b125 Binary files /dev/null and b/assets/proj4/projections/images/ob_tran.png differ diff --git a/assets/proj4/projections/images/ocea.png b/assets/proj4/projections/images/ocea.png new file mode 100644 index 00000000..d9806057 Binary files /dev/null and b/assets/proj4/projections/images/ocea.png differ diff --git a/assets/proj4/projections/images/oea.png b/assets/proj4/projections/images/oea.png new file mode 100644 index 00000000..37a8cf10 Binary files /dev/null and b/assets/proj4/projections/images/oea.png differ diff --git a/assets/proj4/projections/images/omerc.png b/assets/proj4/projections/images/omerc.png new file mode 100644 index 00000000..e81d8a4d Binary files /dev/null and b/assets/proj4/projections/images/omerc.png differ diff --git a/assets/proj4/projections/images/ortel.png b/assets/proj4/projections/images/ortel.png new file mode 100644 index 00000000..2c83692b Binary files /dev/null and b/assets/proj4/projections/images/ortel.png differ diff --git a/assets/proj4/projections/images/ortho.png b/assets/proj4/projections/images/ortho.png new file mode 100644 index 00000000..7ccd542e Binary files /dev/null and b/assets/proj4/projections/images/ortho.png differ diff --git a/assets/proj4/projections/images/patterson.png b/assets/proj4/projections/images/patterson.png new file mode 100644 index 00000000..c8e4fba5 Binary files /dev/null and b/assets/proj4/projections/images/patterson.png differ diff --git a/assets/proj4/projections/images/pconic.png b/assets/proj4/projections/images/pconic.png new file mode 100644 index 00000000..b0b9a0d8 Binary files /dev/null and b/assets/proj4/projections/images/pconic.png differ diff --git a/assets/proj4/projections/images/peirce_q_diamond.png b/assets/proj4/projections/images/peirce_q_diamond.png new file mode 100644 index 00000000..b8a84967 Binary files /dev/null and b/assets/proj4/projections/images/peirce_q_diamond.png differ diff --git a/assets/proj4/projections/images/peirce_q_horizontal.png b/assets/proj4/projections/images/peirce_q_horizontal.png new file mode 100644 index 00000000..d6612e93 Binary files /dev/null and b/assets/proj4/projections/images/peirce_q_horizontal.png differ diff --git a/assets/proj4/projections/images/peirce_q_square.png b/assets/proj4/projections/images/peirce_q_square.png new file mode 100644 index 00000000..485b23a1 Binary files /dev/null and b/assets/proj4/projections/images/peirce_q_square.png differ diff --git a/assets/proj4/projections/images/poly.png b/assets/proj4/projections/images/poly.png new file mode 100644 index 00000000..35389ca4 Binary files /dev/null and b/assets/proj4/projections/images/poly.png differ diff --git a/assets/proj4/projections/images/putp1.png b/assets/proj4/projections/images/putp1.png new file mode 100644 index 00000000..83c6b950 Binary files /dev/null and b/assets/proj4/projections/images/putp1.png differ diff --git a/assets/proj4/projections/images/putp2.png b/assets/proj4/projections/images/putp2.png new file mode 100644 index 00000000..8d085df5 Binary files /dev/null and b/assets/proj4/projections/images/putp2.png differ diff --git a/assets/proj4/projections/images/putp3.png b/assets/proj4/projections/images/putp3.png new file mode 100644 index 00000000..410a474d Binary files /dev/null and b/assets/proj4/projections/images/putp3.png differ diff --git a/assets/proj4/projections/images/putp3p.png b/assets/proj4/projections/images/putp3p.png new file mode 100644 index 00000000..84b37114 Binary files /dev/null and b/assets/proj4/projections/images/putp3p.png differ diff --git a/assets/proj4/projections/images/putp4p.png b/assets/proj4/projections/images/putp4p.png new file mode 100644 index 00000000..40d19611 Binary files /dev/null and b/assets/proj4/projections/images/putp4p.png differ diff --git a/assets/proj4/projections/images/putp5.png b/assets/proj4/projections/images/putp5.png new file mode 100644 index 00000000..75df124f Binary files /dev/null and b/assets/proj4/projections/images/putp5.png differ diff --git a/assets/proj4/projections/images/putp5p.png b/assets/proj4/projections/images/putp5p.png new file mode 100644 index 00000000..03b71f18 Binary files /dev/null and b/assets/proj4/projections/images/putp5p.png differ diff --git a/assets/proj4/projections/images/putp6.png b/assets/proj4/projections/images/putp6.png new file mode 100644 index 00000000..3dde6faa Binary files /dev/null and b/assets/proj4/projections/images/putp6.png differ diff --git a/assets/proj4/projections/images/putp6p.png b/assets/proj4/projections/images/putp6p.png new file mode 100644 index 00000000..9661b204 Binary files /dev/null and b/assets/proj4/projections/images/putp6p.png differ diff --git a/assets/proj4/projections/images/qsc.png b/assets/proj4/projections/images/qsc.png new file mode 100644 index 00000000..e8812cf8 Binary files /dev/null and b/assets/proj4/projections/images/qsc.png differ diff --git a/assets/proj4/projections/images/qua_aut.png b/assets/proj4/projections/images/qua_aut.png new file mode 100644 index 00000000..e8a276b1 Binary files /dev/null and b/assets/proj4/projections/images/qua_aut.png differ diff --git a/assets/proj4/projections/images/rhealpix.png b/assets/proj4/projections/images/rhealpix.png new file mode 100644 index 00000000..dc1c344a Binary files /dev/null and b/assets/proj4/projections/images/rhealpix.png differ diff --git a/assets/proj4/projections/images/robin.png b/assets/proj4/projections/images/robin.png new file mode 100644 index 00000000..8d4b38c7 Binary files /dev/null and b/assets/proj4/projections/images/robin.png differ diff --git a/assets/proj4/projections/images/rouss.png b/assets/proj4/projections/images/rouss.png new file mode 100644 index 00000000..5a990a74 Binary files /dev/null and b/assets/proj4/projections/images/rouss.png differ diff --git a/assets/proj4/projections/images/rpoly.png b/assets/proj4/projections/images/rpoly.png new file mode 100644 index 00000000..556f4807 Binary files /dev/null and b/assets/proj4/projections/images/rpoly.png differ diff --git a/assets/proj4/projections/images/sinu.png b/assets/proj4/projections/images/sinu.png new file mode 100644 index 00000000..b2130431 Binary files /dev/null and b/assets/proj4/projections/images/sinu.png differ diff --git a/assets/proj4/projections/images/som.png b/assets/proj4/projections/images/som.png new file mode 100644 index 00000000..5fb3b1fe Binary files /dev/null and b/assets/proj4/projections/images/som.png differ diff --git a/assets/proj4/projections/images/somerc.png b/assets/proj4/projections/images/somerc.png new file mode 100644 index 00000000..6b5de925 Binary files /dev/null and b/assets/proj4/projections/images/somerc.png differ diff --git a/assets/proj4/projections/images/stere.png b/assets/proj4/projections/images/stere.png new file mode 100644 index 00000000..030dd5f6 Binary files /dev/null and b/assets/proj4/projections/images/stere.png differ diff --git a/assets/proj4/projections/images/sterea.png b/assets/proj4/projections/images/sterea.png new file mode 100644 index 00000000..1d82af74 Binary files /dev/null and b/assets/proj4/projections/images/sterea.png differ diff --git a/assets/proj4/projections/images/tcc.png b/assets/proj4/projections/images/tcc.png new file mode 100644 index 00000000..e44c0d01 Binary files /dev/null and b/assets/proj4/projections/images/tcc.png differ diff --git a/assets/proj4/projections/images/tcea.png b/assets/proj4/projections/images/tcea.png new file mode 100644 index 00000000..4e956624 Binary files /dev/null and b/assets/proj4/projections/images/tcea.png differ diff --git a/assets/proj4/projections/images/times.png b/assets/proj4/projections/images/times.png new file mode 100644 index 00000000..b7d9c481 Binary files /dev/null and b/assets/proj4/projections/images/times.png differ diff --git a/assets/proj4/projections/images/tissot.png b/assets/proj4/projections/images/tissot.png new file mode 100644 index 00000000..7f19d08f Binary files /dev/null and b/assets/proj4/projections/images/tissot.png differ diff --git a/assets/proj4/projections/images/tmerc.png b/assets/proj4/projections/images/tmerc.png new file mode 100644 index 00000000..8ef73986 Binary files /dev/null and b/assets/proj4/projections/images/tmerc.png differ diff --git a/assets/proj4/projections/images/tobmerc.png b/assets/proj4/projections/images/tobmerc.png new file mode 100644 index 00000000..291bdac9 Binary files /dev/null and b/assets/proj4/projections/images/tobmerc.png differ diff --git a/assets/proj4/projections/images/tpeqd.png b/assets/proj4/projections/images/tpeqd.png new file mode 100644 index 00000000..6d2849b6 Binary files /dev/null and b/assets/proj4/projections/images/tpeqd.png differ diff --git a/assets/proj4/projections/images/tpers.png b/assets/proj4/projections/images/tpers.png new file mode 100644 index 00000000..183de970 Binary files /dev/null and b/assets/proj4/projections/images/tpers.png differ diff --git a/assets/proj4/projections/images/ups.png b/assets/proj4/projections/images/ups.png new file mode 100644 index 00000000..a5445a65 Binary files /dev/null and b/assets/proj4/projections/images/ups.png differ diff --git a/assets/proj4/projections/images/urm5.png b/assets/proj4/projections/images/urm5.png new file mode 100644 index 00000000..93b2504b Binary files /dev/null and b/assets/proj4/projections/images/urm5.png differ diff --git a/assets/proj4/projections/images/urmfps.png b/assets/proj4/projections/images/urmfps.png new file mode 100644 index 00000000..8ce86928 Binary files /dev/null and b/assets/proj4/projections/images/urmfps.png differ diff --git a/assets/proj4/projections/images/utm.png b/assets/proj4/projections/images/utm.png new file mode 100644 index 00000000..dfa7c7ed Binary files /dev/null and b/assets/proj4/projections/images/utm.png differ diff --git a/assets/proj4/projections/images/vandg.png b/assets/proj4/projections/images/vandg.png new file mode 100644 index 00000000..9debed4a Binary files /dev/null and b/assets/proj4/projections/images/vandg.png differ diff --git a/assets/proj4/projections/images/vandg2.png b/assets/proj4/projections/images/vandg2.png new file mode 100644 index 00000000..757df505 Binary files /dev/null and b/assets/proj4/projections/images/vandg2.png differ diff --git a/assets/proj4/projections/images/vandg3.png b/assets/proj4/projections/images/vandg3.png new file mode 100644 index 00000000..70216d18 Binary files /dev/null and b/assets/proj4/projections/images/vandg3.png differ diff --git a/assets/proj4/projections/images/vandg4.png b/assets/proj4/projections/images/vandg4.png new file mode 100644 index 00000000..313d878e Binary files /dev/null and b/assets/proj4/projections/images/vandg4.png differ diff --git a/assets/proj4/projections/images/vitk1.png b/assets/proj4/projections/images/vitk1.png new file mode 100644 index 00000000..9c438fbe Binary files /dev/null and b/assets/proj4/projections/images/vitk1.png differ diff --git a/assets/proj4/projections/images/wag1.png b/assets/proj4/projections/images/wag1.png new file mode 100644 index 00000000..1af43721 Binary files /dev/null and b/assets/proj4/projections/images/wag1.png differ diff --git a/assets/proj4/projections/images/wag2.png b/assets/proj4/projections/images/wag2.png new file mode 100644 index 00000000..e3cbe11e Binary files /dev/null and b/assets/proj4/projections/images/wag2.png differ diff --git a/assets/proj4/projections/images/wag3.png b/assets/proj4/projections/images/wag3.png new file mode 100644 index 00000000..9eac33c3 Binary files /dev/null and b/assets/proj4/projections/images/wag3.png differ diff --git a/assets/proj4/projections/images/wag4.png b/assets/proj4/projections/images/wag4.png new file mode 100644 index 00000000..27a96b5c Binary files /dev/null and b/assets/proj4/projections/images/wag4.png differ diff --git a/assets/proj4/projections/images/wag5.png b/assets/proj4/projections/images/wag5.png new file mode 100644 index 00000000..7ece05f7 Binary files /dev/null and b/assets/proj4/projections/images/wag5.png differ diff --git a/assets/proj4/projections/images/wag6.png b/assets/proj4/projections/images/wag6.png new file mode 100644 index 00000000..723b731f Binary files /dev/null and b/assets/proj4/projections/images/wag6.png differ diff --git a/assets/proj4/projections/images/wag7.png b/assets/proj4/projections/images/wag7.png new file mode 100644 index 00000000..43ed1924 Binary files /dev/null and b/assets/proj4/projections/images/wag7.png differ diff --git a/assets/proj4/projections/images/weren.png b/assets/proj4/projections/images/weren.png new file mode 100644 index 00000000..c2d92dd9 Binary files /dev/null and b/assets/proj4/projections/images/weren.png differ diff --git a/assets/proj4/projections/images/wink1.png b/assets/proj4/projections/images/wink1.png new file mode 100644 index 00000000..673808d9 Binary files /dev/null and b/assets/proj4/projections/images/wink1.png differ diff --git a/assets/proj4/projections/images/wink2.png b/assets/proj4/projections/images/wink2.png new file mode 100644 index 00000000..519b9629 Binary files /dev/null and b/assets/proj4/projections/images/wink2.png differ diff --git a/assets/proj4/projections/images/wintri.png b/assets/proj4/projections/images/wintri.png new file mode 100644 index 00000000..97647d47 Binary files /dev/null and b/assets/proj4/projections/images/wintri.png differ diff --git a/assets/proj4/projections/imoll.rst b/assets/proj4/projections/imoll.rst new file mode 100644 index 00000000..deb669e1 --- /dev/null +++ b/assets/proj4/projections/imoll.rst @@ -0,0 +1,52 @@ +.. _imoll: + +******************************************************************************** +Interrupted Mollweide +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | imoll | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/imoll.png + :width: 500 px + :align: center + :alt: Interrupted Mollweide + + proj-string: ``+proj=imoll`` + + +The Interrupted Mollweide projection is an equal-area projection intended for +making world maps. The projection is comprised of six separate Mollweide +(``homolographic``) projection regions. In contrast with the Interrupted Goode +Homolosine projection, there is no transition latitude, which gives greater +continuity at the cost of greater equatorial distortion. The lobes in this +projection are chosen to emphasize the land area of the Earth. This projection +was first published in 1919 by J. P. Goode :cite:`Goode1919`. + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/imoll_o.rst b/assets/proj4/projections/imoll_o.rst new file mode 100644 index 00000000..1dcb2935 --- /dev/null +++ b/assets/proj4/projections/imoll_o.rst @@ -0,0 +1,54 @@ +.. _imoll_o: + +******************************************************************************** +Interrupted Mollweide (Oceanic View) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | imoll_o | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/imoll_o.png + :width: 500 px + :align: center + :alt: Interrupted Mollweide Oceanic View + + proj-string: ``+proj=imoll_o +lon_0=-160`` + + +The Interrupted Mollweide (Oceanic View) projection is an equal-area projection +intended for making maps of the Earth's oceans. The projection is comprised of +six separate Mollweide (``homolographic``) projection regions. In contrast with +the Interrupted Goode Homolosine (Oceanic View) projection, there is no +transition latitude, which gives greater continuity at the cost of greater +equatorial distortion. The lobes in this projection are chosen to emphasize the +ocean area of the Earth when used with a central longitude of -160 +degrees. This projection was first published in 1919 by J. P. Goode +:cite:`Goode1919`. + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. A value of +lon_0=-160 is recommended. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/imw_p.rst b/assets/proj4/projections/imw_p.rst new file mode 100644 index 00000000..1dad270e --- /dev/null +++ b/assets/proj4/projections/imw_p.rst @@ -0,0 +1,50 @@ +.. _imw_p: + +******************************************************************************** +International Map of the World Polyconic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudoconical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | imw_p | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/imw_p.png + :width: 500 px + :align: center + :alt: International Map of the World Polyconic + + proj-string: ``+proj=imw_p +lat_1=30 +lat_2=-40`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/index.rst b/assets/proj4/projections/index.rst new file mode 100644 index 00000000..8b89244e --- /dev/null +++ b/assets/proj4/projections/index.rst @@ -0,0 +1,168 @@ +.. _projections: + +================================================================================ +Projections +================================================================================ + +Projections are coordinate operations that are technically conversions but since +projections are so fundamental to PROJ we differentiate them from conversions. + +Projections map the spherical 3D space to a flat 2D space. + +.. toctree:: + :maxdepth: 1 + + adams_hemi + adams_ws1 + adams_ws2 + aea + aeqd + airy + aitoff + alsk + apian + august + bacon + bertin1953 + bipc + boggs + bonne + calcofi + cass + cc + ccon + cea + chamb + collg + col_urban + comill + crast + denoy + eck1 + eck2 + eck3 + eck4 + eck5 + eck6 + eqc + eqdc + eqearth + euler + fahey + fouc + fouc_s + gall + geos + gins8 + gn_sinu + gnom + goode + gs48 + gs50 + guyou + hammer + hatano + healpix + rhealpix + igh + igh_o + imoll + imoll_o + imw_p + isea + kav5 + kav7 + krovak + labrd + laea + lagrng + larr + lask + lcc + lcca + leac + lee_os + loxim + lsat + mbt_s + mbt_fps + mbtfpp + mbtfpq + mbtfps + merc + mil_os + mill + misrsom + mod_krovak + moll + murd1 + murd2 + murd3 + natearth + natearth2 + nell + nell_h + nicol + nsper + nzmg + ob_tran + ocea + oea + omerc + ortel + ortho + patterson + pconic + peirce_q + poly + putp1 + putp2 + putp3 + putp3p + putp4p + putp5 + putp5p + putp6 + putp6p + qua_aut + qsc + robin + rouss + rpoly + s2 + sch + sinu + som + somerc + stere + sterea + gstmerc + tcc + tcea + times + tissot + tmerc + tobmerc + tpeqd + tpers + ups + urm5 + urmfps + utm + vandg + vandg2 + vandg3 + vandg4 + vitk1 + wag1 + wag2 + wag3 + wag4 + wag5 + wag6 + wag7 + webmerc + weren + wink1 + wink2 + wintri diff --git a/assets/proj4/projections/isea.rst b/assets/proj4/projections/isea.rst new file mode 100644 index 00000000..686fe8fe --- /dev/null +++ b/assets/proj4/projections/isea.rst @@ -0,0 +1,92 @@ +.. _isea: + +******************************************************************************** +Icosahedral Snyder Equal Area +******************************************************************************** + +Snyder's Icosahedral Equal Area map projection on an icosahedron polyhedral globe +offers relatively low scale and angular distortion. The equations involved are +relatively straight-forward. The interruptions remain a disadvantage, as with +any low-error projection system applied to the entire globe :cite:`Snyder1992`. +This projection is used as a basis for defining discrete global grid hierarchies. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Polyhedral, equal area | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | isea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/isea.png + :width: 500 px + :align: center + :alt: Icosahedral Snyder Equal Area + + proj-string: ``+proj=isea`` + +.. note:: + As the projection is only defined on a sphere, it should only be used + with a spherical ellipsoid e.g., ``+R=6371007.18091875`` for a sphere with the + authalic radius of the WGS84 ellipsoid. For mapping coordinates on the WGS84 + ellipsoid to the authalic sphere, the input latitude should be converted + from geodetic latitude to authalic latitude. A future version may + automatically perform this conversion when using a non-spherical ellipsoid. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +orient= + + Can be set to either ``isea`` or ``pole``. See Snyder's Figure 12 for pole orientation :cite:`Snyder1992`. + + *Defaults to isea* + +.. option:: +azi= + + Azimuth. + + Not supported by the inverse. + + *Defaults to 0.0* + +.. option:: +aperture= + + Not supported by the inverse. + + *Defaults to 3.0* + +.. option:: +resolution= + + Not supported by the inverse. + + *Defaults to 4.0* + +.. option:: +mode= + + Can be either ``plane``, ``di``, ``dd`` or ``hex``. + + Only ``plane`` supported by the inverse. + + *Defaults to plane* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/kav5.rst b/assets/proj4/projections/kav5.rst new file mode 100644 index 00000000..0025260b --- /dev/null +++ b/assets/proj4/projections/kav5.rst @@ -0,0 +1,46 @@ +.. _kav5: + +******************************************************************************** +Kavrayskiy V +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | kav5 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/kav5.png + :width: 500 px + :align: center + :alt: Kavrayskiy V + + proj-string: ``+proj=kav5`` + +.. note:: This projection name may also be transliterated as Kavraisky V. + +Created by Soviet cartographer Vladimir V. Kavrayskiy in 1933 :cite:`Snyder1993`. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Kavrayskiy V projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/kav7.rst b/assets/proj4/projections/kav7.rst new file mode 100644 index 00000000..1808f4ca --- /dev/null +++ b/assets/proj4/projections/kav7.rst @@ -0,0 +1,46 @@ +.. _kav7: + +******************************************************************************** +Kavrayskiy VII +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | kav7 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/kav7.png + :width: 500 px + :align: center + :alt: Kavrayskiy VII + + proj-string: ``+proj=kav7`` + +.. note:: This projection name may also be transliterated as Kavraisky VII. + +Created by Soviet cartographer Vladimir V. Kavrayskiy in 1939 :cite:`Snyder1993`. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Kavrayskiy VII projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/krovak.rst b/assets/proj4/projections/krovak.rst new file mode 100644 index 00000000..6202f1ac --- /dev/null +++ b/assets/proj4/projections/krovak.rst @@ -0,0 +1,76 @@ +.. _krovak: + +******************************************************************************** +Krovak +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but more accurate around Czech Republic and | +| | Slovakia | ++---------------------+----------------------------------------------------------+ +| **Alias** | krovak | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/krovak.png + :width: 500 px + :align: center + :alt: Krovak + + proj-string: ``+proj=krovak`` + +By default, coordinates in the forward direction are output in easting, northing, +and negative in the Czech Republic and Slovakia, with absolute value of +easting/westing being smaller than absolute value of northing/southing. + +See also :ref:`mod_krovak` for a variation of Krovak used with the S-JTSK/05 datum +in the Czech Republic. + +.. note:: Before PROJ 9.4, using other values for x_0 or y_0 than the default 0 + would lead to incorrect results when not using the ``+czech`` switch. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Krovak projection. + + The latitude of pseudo standard parallel is hardcoded to 78.5° and + the ellipsoid to Bessel. + +.. option:: +czech + + Reverse the sign of the output coordinates, as is tradition in the + Czech Republic, to be westing, southing (positive values in Czech Republic + and Slovakia). + +.. option:: +lon_0= + + Longitude of projection center. + + *Defaults to 24°50' (24.8333333333333)* + +.. option:: +lat_0= + + Latitude of projection center. + + *Defaults to 49.5* + +.. option:: +k_0= + + Scale factor. Determines scale factor used in the projection. + + *Defaults to 0.9999* + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/labrd.rst b/assets/proj4/projections/labrd.rst new file mode 100644 index 00000000..c91e38b7 --- /dev/null +++ b/assets/proj4/projections/labrd.rst @@ -0,0 +1,57 @@ +.. _labrd: + +******************************************************************************** +Laborde +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but more accurate around Madagascar | ++---------------------+----------------------------------------------------------+ +| **Alias** | labrd | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/labrd.png + :width: 500 px + :align: center + :alt: Laborde + + proj-string: ``+proj=labrd +lon_0=40 +lat_0=-10`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +lat_0= + + Latitude of projection center. Must not be zero. + + +Optional +-------------------------------------------------------------------------------- + +.. option:: +azi= + + Azimuth of the central line. + + *Defaults to 0.0* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/laea.rst b/assets/proj4/projections/laea.rst new file mode 100644 index 00000000..5434564c --- /dev/null +++ b/assets/proj4/projections/laea.rst @@ -0,0 +1,46 @@ +.. _laea: + +******************************************************************************** +Lambert Azimuthal Equal Area +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | laea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/laea.png + :width: 500 px + :align: center + :alt: Lambert Azimuthal Equal Area + + proj-string: ``+proj=laea`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/lagrng.rst b/assets/proj4/projections/lagrng.rst new file mode 100644 index 00000000..b96fcd32 --- /dev/null +++ b/assets/proj4/projections/lagrng.rst @@ -0,0 +1,54 @@ +.. _lagrng: + +******************************************************************************** +Lagrange +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | lagrng | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/lagrng.png + :width: 500 px + :align: center + :alt: Lagrange + + proj-string: ``+proj=lagrng`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +W= + + The factor :option:`+W` is the ratio of the difference in longitude from the + central meridian to the a circular meridian to 90. :option:`+W` must be a + positive value. + + *Defaults to 2.0* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_1.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/larr.rst b/assets/proj4/projections/larr.rst new file mode 100644 index 00000000..edcf146b --- /dev/null +++ b/assets/proj4/projections/larr.rst @@ -0,0 +1,42 @@ +.. _larr: + +******************************************************************************** +Larrivee +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | larr | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/larr.png + :width: 500 px + :align: center + :alt: Larrivee + + proj-string: ``+proj=larr`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Larrivee projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/lask.rst b/assets/proj4/projections/lask.rst new file mode 100644 index 00000000..a4f9a5db --- /dev/null +++ b/assets/proj4/projections/lask.rst @@ -0,0 +1,42 @@ +.. _lask: + +******************************************************************************** +Laskowski +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | lask | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/lask.png + :width: 500 px + :align: center + :alt: Laskowski + + proj-string: ``+proj=lask`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/lcc.rst b/assets/proj4/projections/lcc.rst new file mode 100644 index 00000000..bf15d76a --- /dev/null +++ b/assets/proj4/projections/lcc.rst @@ -0,0 +1,88 @@ +.. _lcc: + +******************************************************************************** +Lambert Conformal Conic +******************************************************************************** + +A Lambert Conformal Conic projection (LCC) is a conic map projection +used for aeronautical charts, portions of the State Plane Coordinate +System, and many national and regional mapping systems. It is one of +seven projections introduced by Johann Heinrich Lambert in 1772. + +It has several different forms: with one and two standard parallels +(referred to as 1SP and 2SP in EPSG guidance notes). Additionally we +provide "2SP Michigan" form which is very similar to normal 2SP, but +with a scaling factor on the ellipsoid (given as `k_0` parameter). +It is implemented as per EPSG Guidance Note 7-2 (version 54, August +2018, page 25). It is used in a few systems in the EPSG database which +justifies adding this otherwise non-standard projection. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal conic | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal .| +| | One or two standard parallels (1SP and 2SP). | +| | "LCC 2SP Michigan" form can be used by setting | +| | ``+k_0`` parameter to specify ellipsoid scale. | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Best for regions predominantly east–west in extent and | +| | located in the middle north or south latitudes. | ++---------------------+----------------------------------------------------------+ +| **Alias** | lcc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/lcc.png + :width: 500 px + :align: center + :alt: Lambert Conformal Conic + + proj-string: ``+proj=lcc +lon_0=-90 +lat_1=33 +lat_2=45`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/lat_2.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. option:: +k_0= + + This parameter can represent two different values depending on the + form of the projection. In LCC 1SP it determines the scale factor + at natural origin. In LCC 2SP Michigan it determines the ellipsoid + scale factor. + + *Defaults to 1.0.* + +Further reading +############### + +#. `Wikipedia `_ +#. `Wolfram Mathworld `_ +#. `John P. Snyder "Map projections: A working manual" (pp. 104-110) `_ +#. `ArcGIS documentation on "Lambert Conformal Conic" `_ +#. `EPSG Guidance Note 7-2 (version 54, August 2018, page 25) `_ diff --git a/assets/proj4/projections/lcca.rst b/assets/proj4/projections/lcca.rst new file mode 100644 index 00000000..f97c81b4 --- /dev/null +++ b/assets/proj4/projections/lcca.rst @@ -0,0 +1,46 @@ +.. _lcca: + +******************************************************************************** +Lambert Conformal Conic Alternative +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | lcca | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/lcca.png + :width: 500 px + :align: center + :alt: Lambert Conformal Conic Alternative + + proj-string: ``+proj=lcca +lat_0=35`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/leac.rst b/assets/proj4/projections/leac.rst new file mode 100644 index 00000000..7cc0f2d2 --- /dev/null +++ b/assets/proj4/projections/leac.rst @@ -0,0 +1,53 @@ +.. _leac: + +******************************************************************************** +Lambert Equal Area Conic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | leac | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/leac.png + :width: 500 px + :align: center + :alt: Lambert Equal Area Conic + + proj-string: ``+proj=leac`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Lambert Equal Area Conic + projection. + +.. include:: ../options/lat_1.rst + +.. option:: +south + + Sets the second standard parallel to 90°S. When the flag is off the second + standard parallel is set to 90°N. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + diff --git a/assets/proj4/projections/lee_os.rst b/assets/proj4/projections/lee_os.rst new file mode 100644 index 00000000..3f9248ac --- /dev/null +++ b/assets/proj4/projections/lee_os.rst @@ -0,0 +1,40 @@ +.. _lee_os: + +******************************************************************************** +Lee Oblated Stereographic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | lee_os | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/lee_os.png + :width: 500 px + :align: center + :alt: Lee Oblated Stereographic + + proj-string: ``+proj=lee_os`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/loxim.rst b/assets/proj4/projections/loxim.rst new file mode 100644 index 00000000..9813b6e6 --- /dev/null +++ b/assets/proj4/projections/loxim.rst @@ -0,0 +1,44 @@ +.. _loxim: + +******************************************************************************** +Loximuthal +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | loxim | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/loxim.png + :width: 500 px + :align: center + :alt: Loximuthal + + proj-string: ``+proj=loxim`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Loximuthal projection. + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/lsat.rst b/assets/proj4/projections/lsat.rst new file mode 100644 index 00000000..cdac95cf --- /dev/null +++ b/assets/proj4/projections/lsat.rst @@ -0,0 +1,73 @@ +.. _lsat: + +******************************************************************************** +Space oblique for LANDSAT +******************************************************************************** + +Space oblique for LANDSAT is a specialization of :doc:`Space Oblique Mercator` +which specifies the appropriate ascending longitude, inclination angle, and +orbital period for Landsat satellites, based on their satellite designation / id +and path number. + +"Paths" are the repeating ground tracks that are traced by sun-synchronous +satellites like those of the Landsat program. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | lsat | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/lsat.png + :width: 500 px + :align: center + :alt: Space oblique for LANDSAT + + proj-string: ``+proj=lsat +lat_1=-60 +lat_2=60 +lsat=2 +path=2`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +lsat= + + Landsat satellite used for the projection. Value between 1 and 5. + +.. option:: +path= + + Selected path of satellite. Value between 1 and 253 when :option:`+lsat` is + set to 1,2 or 3, otherwise valid input is between 1 and 233. + + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +############### + +#. `Landsat Wikipedia `_ +#. :doc:`Space Oblique Mercator Projection` + diff --git a/assets/proj4/projections/mbt_fps.rst b/assets/proj4/projections/mbt_fps.rst new file mode 100644 index 00000000..614c514a --- /dev/null +++ b/assets/proj4/projections/mbt_fps.rst @@ -0,0 +1,42 @@ +.. _mbt_fps: + +******************************************************************************** +McBryde-Thomas Flat-Pole Sine (No. 2) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mbt_fps | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mbt_fps.png + :width: 500 px + :align: center + :alt: McBryde-Thomas Flat-Pole Sine (No. 2) + + proj-string: ``+proj=mbt_fps`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/mbt_s.rst b/assets/proj4/projections/mbt_s.rst new file mode 100644 index 00000000..300c2749 --- /dev/null +++ b/assets/proj4/projections/mbt_s.rst @@ -0,0 +1,43 @@ +.. _mbt_s: + +******************************************************************************** +McBryde-Thomas Flat-Polar Sine (No. 1) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mbt_s | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mbt_s.png + :width: 500 px + :align: center + :alt: McBryde-Thomas Flat-Polar Sine (No. 1) + + proj-string: ``+proj=mbt_s`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the McBryde-Thomas Flat-Polar Sine + projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/mbtfpp.rst b/assets/proj4/projections/mbtfpp.rst new file mode 100644 index 00000000..ec83d5f5 --- /dev/null +++ b/assets/proj4/projections/mbtfpp.rst @@ -0,0 +1,42 @@ +.. _mbtfpp: + +******************************************************************************** +McBride-Thomas Flat-Polar Parabolic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mbtfpp | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mbtfpp.png + :width: 500 px + :align: center + :alt: McBride-Thomas Flat-Polar Parabolic + + proj-string: ``+proj=mbtfpp`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/mbtfpq.rst b/assets/proj4/projections/mbtfpq.rst new file mode 100644 index 00000000..4ed23e28 --- /dev/null +++ b/assets/proj4/projections/mbtfpq.rst @@ -0,0 +1,42 @@ +.. _mbtfpq: + +******************************************************************************** +McBryde-Thomas Flat-Polar Quartic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mbtfpq | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mbtfpq.png + :width: 500 px + :align: center + :alt: McBryde-Thomas Flat-Polar Quartic + + proj-string: ``+proj=mbtfpq`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/mbtfps.rst b/assets/proj4/projections/mbtfps.rst new file mode 100644 index 00000000..34217e71 --- /dev/null +++ b/assets/proj4/projections/mbtfps.rst @@ -0,0 +1,43 @@ +.. _mbtfps: + +******************************************************************************** +McBryde-Thomas Flat-Polar Sinusoidal +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mbtfps | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mbtfps.png + :width: 500 px + :align: center + :alt: McBryde-Thomas Flat-Polar Sinusoidal + + proj-string: ``+proj=mbtfps`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the McBryde-Thomas Flat-Polar + Sinusoidal projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/merc.rst b/assets/proj4/projections/merc.rst new file mode 100644 index 00000000..6eae9d36 --- /dev/null +++ b/assets/proj4/projections/merc.rst @@ -0,0 +1,197 @@ +.. _merc: + +******************************************************************************** +Mercator +******************************************************************************** + +The Mercator projection is a cylindrical map projection that origins +from the 16th century. It is widely recognized as the first regularly +used map projection. It is a conformal projection in which the equator +projects to a straight line at constant scale. The projection has the +property that a rhumb line, a course of constant heading, projects to a +straight line. This makes it suitable for navigational purposes. + + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but best used near the equator | ++---------------------+----------------------------------------------------------+ +| **Alias** | merc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/merc.png + :width: 500 px + :align: center + :alt: Mercator + + proj-string: ``+proj=merc`` + +Usage +######## + +Applications should be limited to equatorial regions, but is frequently +used for navigational charts with latitude of true scale (:option:`+lat_ts`) specified within +or near chart's boundaries. +It is considered to be inappropriate for world maps because of the gross +distortions in area; for example the projected area of Greenland is +larger than that of South America, despite the fact that Greenland's +area is :math:`\frac18` that of South America :cite:`Snyder1987`. + + +Example using latitude of true scale:: + + $ echo 56.35 12.32 | proj +proj=merc +lat_ts=56.5 + 3470306.37 759599.90 + +Example using scaling factor:: + + echo 56.35 12.32 | proj +proj=merc +k_0=2 + 12545706.61 2746073.80 + + +Note that :option:`+lat_ts` and :option:`+k_0` are mutually exclusive. +If used together, :option:`+lat_ts` takes precedence over :option:`+k_0`. + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +Mathematical definition +####################### + +Spherical form +************** +For the spherical form of the projection we introduce the scaling factor: + +.. math:: + + k_0 = \cos \phi_{ts} + +Forward projection +================== + +.. math:: + + x = k_0R \lambda; \qquad y = k_0R \psi + +.. math:: + + \psi &= \ln \tan \biggl(\frac{\pi}{4} + \frac{\phi}{2} \biggr)\\ + &= \sinh^{-1}\tan\phi + +The quantity :math:`\psi` is the isometric latitude. + + +Inverse projection +================== + +.. math:: + + \lambda = \frac{x}{k_0R}; \qquad \psi = \frac{y}{k_0R} + +.. math:: + + \phi &= \frac{\pi}{2} - 2 \tan^{-1} \exp(-\psi)\\ + &= \tan^{-1}\sinh\psi + + +Ellipsoidal form +**************** + +For the ellipsoidal form of the projection we introduce the scaling factor: + +.. math:: + + k_0 = m( \phi_{ts} ) + +where + +.. math:: + + m(\phi) = \frac{\cos\phi}{\sqrt{1 - e^2\sin^2\phi}} + +:math:`a\,m(\phi)` is the radius of the circle of latitude :math:`\phi`. + +Forward projection +================== +.. math:: + + x = k_0 a \lambda; \qquad y = k_0 a \psi + +.. math:: + + \psi &= \ln\tan\biggl(\frac\pi4 + \frac{\phi}2\biggr) + -\frac12 e + \ln \biggl(\frac{1 + e \sin\phi}{1 - e \sin\phi}\biggr)\\ + &= \sinh^{-1}\tan\phi - e \tanh^{-1}(e \sin\phi) + + +Inverse projection +================== + +.. math:: + + \lambda = \frac{x}{k_0 a}; \quad \psi = \frac{y}{k_0 a} + +The latitude :math:`\phi` is found by inverting the equation for +:math:`\psi`. This follows the method given by :cite:`Karney2011tm`. +Start by introducing the conformal latitude + +.. math:: + \chi = \tan^{-1}\sinh\psi + +The tangents of the latitudes :math:`\tau = \tan\phi` and :math:`\tau' = +\tan\chi = \sinh\psi` are related by + +.. math:: + \tau' = \tau \sqrt{1 + \sigma^2} - \sigma \sqrt{1 + \tau^2} + +where + +.. math:: + \sigma = \sinh\bigl(e \tanh^{-1}(e \tau/\sqrt{1 + \tau^2}) \bigr) + +This is obtained by taking the :math:`\sinh` of the equation for +:math:`\psi` and using the multiple argument formula. The equation for +:math:`\tau'` can be solved to give :math:`\tau` using Newton's method +using :math:`\tau = \tau'/(1 - e^2)` as an initial guess and with the +needed derivative given by + +.. math:: + \frac{d\tau'}{d\tau} = \frac{1 - e^2}{1 + (1 - e^2)\tau^2} + \sqrt{1 + \tau'^2} \sqrt{1 + \tau^2} + +This converges after no more than 2 iterations. Finally set +:math:`\phi=\tan^{-1}\tau`. + +Further reading +############### + +#. `Wikipedia `_ +#. `Wolfram Mathworld `_ + + diff --git a/assets/proj4/projections/mil_os.rst b/assets/proj4/projections/mil_os.rst new file mode 100644 index 00000000..dbf8488f --- /dev/null +++ b/assets/proj4/projections/mil_os.rst @@ -0,0 +1,40 @@ +.. _mil_os: + +******************************************************************************** +Miller Oblated Stereographic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | mil_os | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mil_os.png + :width: 500 px + :align: center + :alt: Miller Oblated Stereographic + + proj-string: ``+proj=mil_os`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/mill.rst b/assets/proj4/projections/mill.rst new file mode 100644 index 00000000..83447ad5 --- /dev/null +++ b/assets/proj4/projections/mill.rst @@ -0,0 +1,91 @@ +.. _mill: + +******************************************************************************** +Miller Cylindrical +******************************************************************************** + +The Miller cylindrical projection is a modified Mercator projection, proposed by Osborn Maitland Miller in 1942. +The latitude is scaled by a factor of :math:`\frac{4}{5}`, projected according to Mercator, and then the result is multiplied by :math:`\frac{5}{4}` to retain scale along the equator. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Neither conformal nor equal area cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse spherical | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but best used near the equator | ++---------------------+----------------------------------------------------------+ +| **Alias** | mill | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/mill.png + :width: 500 px + :align: center + :alt: Miller Cylindrical + + proj-string: ``+proj=mill`` + +Usage +######## + +The Miller Cylindrical projection is used for world maps and in several atlases, +including the National Atlas of the United States (USGS, 1970, p. 330-331) :cite:`Snyder1987`. + +Example using Central meridian 90°W:: + + $ echo -100 35 | proj +proj=mill +lon_0=90w + -1113194.91 4061217.24 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Mathematical definition +####################### + +The formulas describing the Miller projection are all taken from :cite:`Snyder1987`. + + +Forward projection +================== + +.. math:: + + x = \lambda + +.. math:: + + y = 1.25 * \ln \left[ \tan \left(\frac{\pi}{4} + 0.4 * \phi \right) \right] + + +Inverse projection +================== + +.. math:: + + \lambda = x + +.. math:: + + \phi = 2.5 * ( \arctan \left[ e^{0.8 * y} \right] - \frac{\pi}{4} ) + +Further reading +############### + +#. `Wikipedia `_ + diff --git a/assets/proj4/projections/misrsom.rst b/assets/proj4/projections/misrsom.rst new file mode 100644 index 00000000..3c8fda93 --- /dev/null +++ b/assets/proj4/projections/misrsom.rst @@ -0,0 +1,67 @@ +.. _misrsom: + +******************************************************************************** +Space oblique for MISR +******************************************************************************** + +Space oblique for MISR is a specialization of :doc:`Space Oblique Mercator` +which specifies the appropriate ascending longitude, inclination angle, and orbital +period for Multi-angle Imaging Spectroradiometer (MISR) orbits, based on the path +number for the orbit. + +"Paths" are the repeating ground tracks that are traced by sun-synchronous +satellites like Terra on which the MISR instrument is aboard. + + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | misrsom | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/misrsom.png + :width: 500 px + :align: center + :alt: Space oblique for MISR + + proj-string: ``+proj=misrsom +path=1`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +path= + + Selected path of satellite. Value between 1 and 233. + + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +#. `MISR Wikipedia `_ +#. `Terra Wikipedia `_ +#. :doc:`Space Oblique Mercator Projection` + diff --git a/assets/proj4/projections/mod_krovak.rst b/assets/proj4/projections/mod_krovak.rst new file mode 100644 index 00000000..6b72c88b --- /dev/null +++ b/assets/proj4/projections/mod_krovak.rst @@ -0,0 +1,77 @@ +.. _mod_krovak: + +******************************************************************************** +Modified Krovak +******************************************************************************** + +.. versionadded:: 9.4.0 + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Czech Republic | ++---------------------+----------------------------------------------------------+ +| **Alias** | mod_krovak | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/krovak.png + :width: 500 px + :align: center + :alt: Modified Krovak + + proj-string: ``+proj=mod_krovak`` + +Modified Krovak builts upon traditional :ref:`krovak`, with corrective terms that +are better suited when using it with the S-JTSK/05 datum. This method is specific +to the Czech Republic. Due to the corrective terms, this projection method is +no longer strictly conformal. + +By default, coordinates in the forward direction are output in easting, northing, +and negative in the Czech Republic, with absolute value of easting/westing +being smaller than absolute value of northing/southing. +To distinguish it from regular Krovak, the usual value for ``+x_0`` and ``+y_0`` +in Modified Krovak is typically 5,000,000. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Modified Krovak projection. + + The latitude of pseudo standard parallel is hardcoded to 78.5° and + the ellipsoid to Bessel. + +.. option:: +czech + + Reverse the sign of the output coordinates, as is tradition in the + Czech Republic, to be westing, southing (positive values in Czech Republic) + +.. option:: +lon_0= + + Longitude of projection center. + + *Defaults to 24°50' (24.8333333333333)* + +.. option:: +lat_0= + + Latitude of projection center. + + *Defaults to 49.5* + +.. option:: +k_0= + + Scale factor. Determines scale factor used in the projection. + + *Defaults to 0.9999* + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/moll.rst b/assets/proj4/projections/moll.rst new file mode 100644 index 00000000..e38131d4 --- /dev/null +++ b/assets/proj4/projections/moll.rst @@ -0,0 +1,42 @@ +.. _moll: + +******************************************************************************** +Mollweide +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | moll | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/moll.png + :width: 500 px + :align: center + :alt: Mollweide + + proj-string: ``+proj=moll`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/murd1.rst b/assets/proj4/projections/murd1.rst new file mode 100644 index 00000000..c52de4ce --- /dev/null +++ b/assets/proj4/projections/murd1.rst @@ -0,0 +1,50 @@ +.. _murd1: + +******************************************************************************** +Murdoch I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | murd1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/murd1.png + :width: 500 px + :align: center + :alt: Murdoch I + + proj-string: ``+proj=murd1 +lat_1=30 +lat_2=50`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/murd2.rst b/assets/proj4/projections/murd2.rst new file mode 100644 index 00000000..e9dbb1a6 --- /dev/null +++ b/assets/proj4/projections/murd2.rst @@ -0,0 +1,50 @@ +.. _murd2: + +******************************************************************************** +Murdoch II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | murd2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/murd2.png + :width: 500 px + :align: center + :alt: Murdoch II + + proj-string: ``+proj=murd2 +lat_1=30 +lat_2=50`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/murd3.rst b/assets/proj4/projections/murd3.rst new file mode 100644 index 00000000..194ea31f --- /dev/null +++ b/assets/proj4/projections/murd3.rst @@ -0,0 +1,50 @@ +.. _murd3: + +******************************************************************************** +Murdoch III +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | murd3 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/murd3.png + :width: 500 px + :align: center + :alt: Murdoch III + + proj-string: ``+proj=murd3 +lat_1=30 +lat_2=50`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/natearth.rst b/assets/proj4/projections/natearth.rst new file mode 100644 index 00000000..4ed5b638 --- /dev/null +++ b/assets/proj4/projections/natearth.rst @@ -0,0 +1,65 @@ +.. _natearth: + +******************************************************************************** +Natural Earth +******************************************************************************** ++---------------------+--------------------------------------------------------+ +| **Classification** | Pseudo cylindrical | ++---------------------+--------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+--------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+--------------------------------------------------------+ +| **Alias** | natearth | ++---------------------+--------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+--------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+--------------------------------------------------------+ + + + +.. figure:: ./images/natearth.png + :width: 500 px + :align: center + :alt: Natural Earth + + proj-string: ``+proj=natearth`` + +The Natural Earth projection is intended for making world maps. A distinguishing trait +is its slightly rounded corners fashioned to emulate the spherical shape of Earth. +The meridians (except for the central meridian) bend acutely inward as they approach +the pole lines, giving the projection a hint of three-dimensionality. This bending +also suggests that the meridians converge at the poles instead of truncating at the +top and bottom edges. The distortion characteristics of the Natural Earth projection +compare favorably to other world map projections. + + +Usage +############################################################################### + +The Natural Earth projection has no special options so usage is simple. Here is +an example of an inverse projection on a sphere with a radius of 7500 m:: + + $ echo 3500 -8000 | proj -I +proj=natearth +a=7500 + 37d54'6.091"E 61d23'4.582"S + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. `Wikipedia `_ diff --git a/assets/proj4/projections/natearth2.rst b/assets/proj4/projections/natearth2.rst new file mode 100644 index 00000000..a5128ea9 --- /dev/null +++ b/assets/proj4/projections/natearth2.rst @@ -0,0 +1,49 @@ +.. _natearth2: + +******************************************************************************** +Natural Earth II +******************************************************************************** ++---------------------+--------------------------------------------------------+ +| **Classification** | Pseudo cylindrical | ++---------------------+--------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+--------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+--------------------------------------------------------+ +| **Alias** | natearth2 | ++---------------------+--------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+--------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+--------------------------------------------------------+ + + + +.. figure:: ./images/natearth2.png + :width: 500 px + :align: center + :alt: Natural Earth II + + proj-string: ``+proj=natearth2`` + +The Natural Earth II projection is intended for making world maps. At high +latitudes, meridians bend steeply toward short pole lines resulting in a map +with highly rounded corners that resembles an elongated globe. + +See :cite:`Savric2015` + + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/nell.rst b/assets/proj4/projections/nell.rst new file mode 100644 index 00000000..2619d0b3 --- /dev/null +++ b/assets/proj4/projections/nell.rst @@ -0,0 +1,42 @@ +.. _nell: + +******************************************************************************** +Nell +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | nell | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/nell.png + :width: 500 px + :align: center + :alt: Nell + + proj-string: ``+proj=nell`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/nell_h.rst b/assets/proj4/projections/nell_h.rst new file mode 100644 index 00000000..5b4a45df --- /dev/null +++ b/assets/proj4/projections/nell_h.rst @@ -0,0 +1,42 @@ +.. _nell_h: + +******************************************************************************** +Nell-Hammer +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | nell_h | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/nell_h.png + :width: 500 px + :align: center + :alt: Nell-Hammer + + proj-string: ``+proj=nell_h`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/nicol.rst b/assets/proj4/projections/nicol.rst new file mode 100644 index 00000000..0b7f0251 --- /dev/null +++ b/assets/proj4/projections/nicol.rst @@ -0,0 +1,42 @@ +.. _nicol: + +******************************************************************************** +Nicolosi Globular +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudoconical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | nicol | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/nicol.png + :width: 500 px + :align: center + :alt: Nicolosi Globular + + proj-string: ``+proj=nicol`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/nsper.rst b/assets/proj4/projections/nsper.rst new file mode 100644 index 00000000..d99c7932 --- /dev/null +++ b/assets/proj4/projections/nsper.rst @@ -0,0 +1,53 @@ +.. _nsper: + +******************************************************************************** +Near-sided perspective +******************************************************************************** + +The near-sided perspective projection simulates a view from a height +:math:`h` similar to how a satellite in orbit would see it. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal. Neither conformal nor equal area. | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, although for one hemisphere at a time. | ++---------------------+----------------------------------------------------------+ +| **Alias** | nsper | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/nsper.png + :width: 500 px + :align: center + :alt: Near-sided perspective + + proj-string: ``+proj=nsper +h=3000000 +lat_0=-20 +lon_0=145`` + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. include:: ../options/h.rst + +Optional +------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/nzmg.rst b/assets/proj4/projections/nzmg.rst new file mode 100644 index 00000000..f59ae6bb --- /dev/null +++ b/assets/proj4/projections/nzmg.rst @@ -0,0 +1,17 @@ +.. _nzmg: + +******************************************************************************** +New Zealand Map Grid (EPSG:27200) +******************************************************************************** + +.. figure:: ./images/nzmg.png + :width: 500 px + :align: center + :alt: New Zealand Map Grid (EPSG:27200) + + proj-string: ``+proj=nzmg`` + +Parameters +################################################################################ + +.. note:: All standard projection parameters are hard-coded for this projection diff --git a/assets/proj4/projections/ob_tran.rst b/assets/proj4/projections/ob_tran.rst new file mode 100644 index 00000000..b25d065c --- /dev/null +++ b/assets/proj4/projections/ob_tran.rst @@ -0,0 +1,120 @@ +.. _ob_tran: + +******************************************************************************** +General Oblique Transformation +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | ob_tran | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/ob_tran.png + :width: 500 px + :align: center + :alt: General Oblique Transformation + + proj-string: ``+proj=ob_tran +o_proj=moll +o_lon_p=40 +o_lat_p=50 +lon_0=60`` + +Usage +################################################################################ + +All of the projections of spherical library can be used as an +oblique projection by means of the General Oblique Transformation. The user +performs the oblique transformation by selecting the oblique projection +``+proj=ob_tran``, specifying the translation factors, :option:`+o_lat_p`, and +:option:`+o_lon_p`, and the projection to be used, :option:`+o_proj`. In the +example of the Fairgrieve projection, the latitude and longitude of the North pole +of the unrotated geographic CRS, :math:`\alpha` and :math:`\beta` respectively, +expressed in the rotated geographic CRS, are to be placed +at 45°N and 90°W and the :ref:`moll` projection is used. Because the central meridian +of the translated coordinates will follow the :math:`\beta` meridian it is +necessary to translate the translated system so that the Greenwich meridian +will pass through the center of the projection by offsetting the central meridian. + +The final control for this projection is:: + + +proj=ob_tran +o_proj=moll +o_lat_p=45 +o_lon_p=-90 +lon_0=-90 + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +o_proj= + + Oblique projection. + +In addition to specifying an oblique projection, *how* to rotate the projection +should be specified. This is done in one of three ways: Define a new pole, +rotate the projection about a given point or define a new "equator" spanned by +two points on the sphere. See the details below. + +New pole +................................................................................ + +.. option:: +o_lat_p= + + Latitude of the North pole of the unrotated source CRS, expressed in the rotated geographic CRS. + +.. option:: +o_lon_p= + + Longitude of the North pole of the unrotated source CRS, expressed in the rotated geographic CRS. + +Rotate about point +................................................................................ + +.. option:: +o_alpha= + + Angle to rotate the projection with. + +.. option:: +o_lon_c= + + Longitude of the point the projection will be rotated about. + +.. option:: +o_lat_c= + + Latitude of the point the projection will be rotated about. + +New "equator" points +................................................................................ + +.. option:: +o_lon_1= + + Longitude of first point. + +.. option:: +o_lat_1= + + Latitude of first point. + +.. option:: +o_lon_2= + + Longitude of second point. + +.. option:: +o_lat_2= + + Latitude of second point. + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/ocea.rst b/assets/proj4/projections/ocea.rst new file mode 100644 index 00000000..59b3abee --- /dev/null +++ b/assets/proj4/projections/ocea.rst @@ -0,0 +1,82 @@ +.. _ocea: + +******************************************************************************** +Oblique Cylindrical Equal Area +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | ocea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/ocea.png + :width: 500 px + :align: center + :alt: Oblique Cylindrical Equal Area + + proj-string: ``+proj=ocea`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +For the Oblique Cylindrical Equal Area projection a pole of rotation is needed. +The pole can be defined in two ways: By a point and azimuth or by providing two +points that make up the pole. + +Point & azimuth +................................................................................ + +.. option:: +lonc= + + Longitude of rotational pole point. + +.. option:: +alpha= + + Angle of rotational pole. + +Two points +................................................................................ + +.. option:: +lon_1= + + Longitude of first point. + +.. option:: +lat_1= + + Latitude of first point. + +.. option:: +lon_2= + + Longitude of second point. + +.. option:: +lat_2= + + Latitude of second point. + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/oea.rst b/assets/proj4/projections/oea.rst new file mode 100644 index 00000000..a3db1722 --- /dev/null +++ b/assets/proj4/projections/oea.rst @@ -0,0 +1,54 @@ +.. _oea: + +******************************************************************************** +Oblated Equal Area +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | oea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/oea.png + :width: 500 px + :align: center + :alt: Oblated Equal Area + + proj-string: ``+proj=oea +m=1 +n=2`` + +Described in :cite:`Snyder1988`. + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +m= + +.. option:: +n= + +Optional +-------------------------------------------------------------------------------- + +.. option:: +theta= + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/omerc.rst b/assets/proj4/projections/omerc.rst new file mode 100644 index 00000000..00c40d58 --- /dev/null +++ b/assets/proj4/projections/omerc.rst @@ -0,0 +1,209 @@ +.. _omerc: + +******************************************************************************** +Oblique Mercator +******************************************************************************** + +The Oblique Mercator projection is a cylindrical map projection that closes the +gap between the Mercator and the Transverse Mercator projections. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, but reasonably accurate only within 15 degrees | +| | of the oblique central line | ++---------------------+----------------------------------------------------------+ +| **Alias** | omerc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/omerc.png + :width: 500 px + :align: center + :alt: Oblique Mercator + + proj-string: ``+proj=omerc +lat_1=45 +lat_2=55`` + + +Figuratively, the cylinder used for developing the Mercator projection touches +the planet along the Equator, while that of the Transverse Mercator touches the +planet along a meridian, i.e. along a line perpendicular to the Equator. + +The cylinder for the Oblique Mercator, however, touches the planet along a line +at an arbitrary angle with the Equator. Hence, the Oblique Mercator projection +is useful for mapping areas having their greatest extent along a direction that +is neither north-south, nor east-west. + +The Mercator and the Transverse Mercator projections are both limiting forms of +the Oblique Mercator: The Mercator projection is equivalent to an Oblique Mercator +with central line along the Equator, while the Transverse Mercator is equivalent +to an Oblique Mercator with central line along a meridian. + +For the sphere, the construction of the Oblique Mercator projection can be +imagined as "tilting the cylinder of a plain Mercator projection", +so the cylinder, instead of touching the equator, touches an arbitrary great circle +on the sphere. The great circle is defined by the tilt angle of the central line, +hence putting land masses along that great circle near the centre of the map, +where the Equator would go in the plain Mercator case. + +The ellipsoidal case, developed by Hotine, and refined by Snyder :cite:`Snyder1987` +is more complex, involving initial steps projecting from the ellipsoid to another +curved surface, the "aposphere", then projection from the aposphere to the skew +uv-plane, before finally rectifying the skew uv-plane onto the map XY plane. + + +Usage +######## + +The tilt angle (azimuth) of the central line can be given in two different ways. +In the first case, the azimuth is given directly, using the option :option:`+alpha` +and defining the centre of projection using the options :option:`+lonc` and +:option:`+lat_0`. +In the second case, the azimuth is given indirectly by specifying two points on +the central line, using the options +:option:`+lat_1`, :option:`+lon_1`, :option:`+lat_2`, and :option:`+lon_2`. + +Example: Verify that the Mercator, and Transverse Mercator (on a sphere), +projections are limiting forms of the Oblique Mercator + +:: + + $ echo 12 55 | proj +proj=merc +ellps=GRS80 + 1335833.89 7326837.71 + + $ echo 12 55 | proj +proj=omerc +alpha=90 +ellps=GRS80 + 1335833.89 7326837.71 + + $ echo 12 55 | proj +proj=omerc +alpha=0 +R=6400000 + 766869.97 6209742.96 + + # Same, with azimuth given indirectly via two points: + $ echo 12 55 | proj +proj=omerc +lon_1=0 +lat_1=-1 +lon_2=0 +lat_2=0 +R=6400000 + 766869.97 6209742.96 + + $ echo 12 55 | proj +proj=tmerc +R=6400000 + 766869.97 6209742.96 + +Example: Second case - indirectly given azimuth + +:: + + $ echo 12 55 | proj +proj=omerc +lon_1=-1 +lat_1=1 +lon_2=0 +lat_2=0 +ellps=GRS80 + 349567.57 6839490.50 + + # Same, with directly given azimuth, (via: echo 0 0 1 -1|geod -I -f %.7f +ellps=GRS80): + $ echo 12 55 | proj +proj=omerc +alpha=-45.1880402 +ellps=GRS80 + 349567.57 6839490.50 + + +Example: An approximation of the Danish "System 34" from :cite:`Rittri2012` + +:: + + $ echo 10.536498003 56.229892362 | cs2cs +proj=longlat +ellps=GRS80 +to +proj=omerc +axis=wnu +lonc=9.46 +lat_0=56.13333333 +x_0=-266906.229 +y_0=189617.957 +k=0.9999537 +alpha=-0.76324 +gamma=0 +ellps=GRS80 + 200000.13 199999.89 + +The input coordinate represents the System 34 datum point "Agri Bavnehoj", with coordinates +(200000, 200000) by definition. So at the datum point, the approximation is off by about 17 cm. +This use case represents a datum shift from a cylinder projection on an old, slightly +misaligned datum, to a similar projection on a modern datum. + + +Parameters +################################################################################ + + +Central point and azimuth method +-------------------------------------------------------------------------------- + +.. option:: +alpha= + + Azimuth of centerline clockwise from north at the center point of the line. + If :option:`+gamma` is not given then :option:`+alpha` determines the value of + :option:`+gamma`. + +.. option:: +gamma= + + Azimuth of centerline clockwise from north of the rectified + bearing of centre line. If :option:`+alpha` is not given, then + :option:`+gamma` is used to determine :option:`+alpha`. + + If specifying only :option:`+gamma` without :option:`+alpha`, the maximum + value of the absolute value of :option:`+gamma` is a function of the + absolute value of :option:`+lat_0`, equal to :math:`90° - |\phi_0|` on a + sphere and slightly above on a non-spherical ellipsoid. + +.. option:: +lonc= + + Longitude of the projection centre. Note that this value is used to + override the :option:`+lon_0` parameter, so the latter should not be + specified as it would get ignored. + +.. option:: +lat_0= + + Latitude of the projection centre. + +Two point method +-------------------------------------------------------------------------------- + +.. option:: +lon_1= + + Longitude of first point. + +.. option:: +lat_1= + + Latitude of first point. + +.. option:: +lon_2= + + Longitude of second point. + +.. option:: +lat_2= + + Latitude of second point. + +Optional +-------------------------------------------------------------------------------- + +.. option:: +no_rot + + No rectification (not "no rotation" as one may well assume). + Do not take the last step from the skew uv-plane to the map + XY plane. + + .. note:: This option is probably only marginally useful, but remains for (mostly) historical reasons. + +.. option:: +no_off + + Do not offset origin to center of projection. + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Caveats +####### + +Note for the two-point method no rectification is done, + +:: + + echo 0 0|proj -I +proj=omerc +R=6400000 +lonc=-87 +lat_0=42 +alpha=0 + 87dW 42dN + echo 0 0|proj -I +proj=omerc +R=6400000 +lonc=-87 +lat_0=42 +alpha=0 +no_rot + 87dW 0dS + echo 0 0|proj -I +proj=omerc +R=6400000 +lon_1=-87 +lat_1=42 +lon_2=-87 +lat_2=43 + 87dW 0dN + +Thus, just as was noted above regarding +no_rot, the two-point method +itself is also probably only marginally useful. diff --git a/assets/proj4/projections/ortel.rst b/assets/proj4/projections/ortel.rst new file mode 100644 index 00000000..f6fa5f3c --- /dev/null +++ b/assets/proj4/projections/ortel.rst @@ -0,0 +1,42 @@ +.. _ortel: + +******************************************************************************** +Ortelius Oval +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | ortel | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/ortel.png + :width: 500 px + :align: center + :alt: Ortelius Oval + + proj-string: ``+proj=ortel`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/ortho.rst b/assets/proj4/projections/ortho.rst new file mode 100644 index 00000000..7a5d62c5 --- /dev/null +++ b/assets/proj4/projections/ortho.rst @@ -0,0 +1,76 @@ +.. _ortho: + +******************************************************************************** +Orthographic +******************************************************************************** + +The orthographic projection is a perspective azimuthal projection centered +around a given latitude and longitude. + ++---------------------+--------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+--------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+--------------------------------------------------------+ +| **Defined area** | Global, although only one hemisphere can be seen at a | +| | time | ++---------------------+--------------------------------------------------------+ +| **Alias** | ortho | ++---------------------+--------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+--------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+--------------------------------------------------------+ + +.. figure:: ./images/ortho.png + :width: 500 px + :align: center + :alt: Orthographic + + proj-string: ``+proj=ortho`` + + +.. note:: Before PROJ 7.2, only the spherical formulation was implemented. If + wanting to replicate PROJ < 7.2 results with newer versions, the + ellipsoid must be forced to a sphere, for example by adding a ``+f=0`` + parameter. + +.. note:: Parameters ``k_0`` and ``alpha`` are added in PROJ 9.5.0 + +This projection method corresponds to ``EPSG:9840`` +(or ``EPSG:1130`` with ``k_0`` or ``alpha``). + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. option:: +alpha= + + .. versionadded:: 9.5.0 + + Azimuth clockwise from north at the center of projection. + + *Defaults to 0.0.* + +.. option:: +k_0= + + .. versionadded:: 9.5.0 + + Scale factor. Determines scale factor used in the projection. + + *Defaults to 1.0.* + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/patterson.rst b/assets/proj4/projections/patterson.rst new file mode 100644 index 00000000..b68a14d0 --- /dev/null +++ b/assets/proj4/projections/patterson.rst @@ -0,0 +1,46 @@ +.. _patterson: + +******************************************************************************** +Patterson +******************************************************************************** + +The Patterson projection is a cylindrical map projection designed for +general-purpose mapmaking. + +See :cite:`Patterson2014` + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | patterson | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/patterson.png + :width: 500 px + :align: center + :alt: Patterson + + proj-string: ``+proj=patterson`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/pconic.rst b/assets/proj4/projections/pconic.rst new file mode 100644 index 00000000..8c486151 --- /dev/null +++ b/assets/proj4/projections/pconic.rst @@ -0,0 +1,50 @@ +.. _pconic: + +******************************************************************************** +Perspective Conic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | pconic | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/pconic.png + :width: 500 px + :align: center + :alt: Perspective Conic + + proj-string: ``+proj=pconic +lat_1=25 +lat_2=75`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/peirce_q.rst b/assets/proj4/projections/peirce_q.rst new file mode 100644 index 00000000..57504032 --- /dev/null +++ b/assets/proj4/projections/peirce_q.rst @@ -0,0 +1,119 @@ +.. _peirce_q: + +******************************************************************************** +Peirce Quincuncial +******************************************************************************** + +The Peirce Quincuncial projection is a conformal map projection +that transforms the circle of the northern hemisphere into a square, +and the southern hemisphere split into four triangles arranged +around the square to form a quincunx. The resulting projection +is a regular diamond shape or can be rotated to form a square. +The resulting tile can be infinitely tessellated. Though this implementation +defaults to a central meridian of 0, it is more common to use a central +meridian of around 25 to optimise the distortions. Peirce's original +published map from 1879 used a central meridian of approx -70. +The diamond and square versions can be produced using the +``+shape=diamond`` and ``+shape=square`` options respectively. +This implementation includes an alternative lateral projection +which places hemispheres side-by-side (``+shape=horizontal`` or +``+shape=vertical``). Combined with a general oblique transformation, +this can be used to produced a Grieger Triptychial projection +(see example below). + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | peirce_q | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/peirce_q_square.png + :width: 500 px + :align: center + :alt: Peirce Quincuncial (Square) + + proj-string: ``+proj=peirce_q +lon_0=25 +shape=square`` + +.. figure:: ./images/peirce_q_diamond.png + :width: 500 px + :align: center + :alt: Peirce Quincuncial (Diamond) + + proj-string: ``+proj=peirce_q +lon_0=25 +shape=diamond`` + +.. figure:: ./images/peirce_q_horizontal.png + :width: 500 px + :align: center + :alt: Peirce Quincuncial (Horizontal) + + proj-string: ``+proj=peirce_q +lon_0=25 +shape=horizontal`` + +.. figure:: ./images/grieger_triptychial.png + :width: 500 px + :align: center + :alt: Grieger Triptychial + + proj-string: ``+proj=pipeline +step +proj=ob_tran +o_proj=peirce_q +o_lat_p=-45 +o_lon_p=45 +o_type=horizontal +o_scrollx=-0.25 +step +proj=affine +s11=-1 +s12=0 +s21=0 +s22=-1`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. option:: +shape=square/diamond/horizontal/vertical/nhemisphere/shemisphere + + .. versionadded:: 9.0 + + *Defaults to diamond.* + + .. warning:: This option was wrongly introduced introduced in 8.2.1 with the + ``type`` name, which was inappropriate as it conflicted with + the ``+type=crs`` general hint. + + Indicates the shape of transformation applied to the southern hemisphere: + ``square`` and ``diamond`` represent the traditional quincuncial form suggested + by Peirce with the southern hemisphere divided into 4 triangles and reflected + outward from the northern hemisphere. The ``square`` shape is rotated by 45 + degrees to produce the conventional square presentation. The origin lies at + the centre of the square or diamond. + + By contrast, the ``horizontal`` and ``vertical`` forms reflect the southern + hemisphere laterally across the x or y axis respectively to produce a rectangular + form. The origin lies at the centre of the rectangle. + + The other two types, ``nhemisphere`` and ``shemisphere``, discard latitudes of less + than 0 or more than 0, respectively, to allow single hemispheres to be selected. + The origin lies at the centre of the square or diamond. + +.. option:: +scrollx= + + For ``horizontal`` shape allows a scalar circular scroll of resulting x coordinates + to shift sections of the projection to the other horizontal side of the map. + + *Defaults to 0.0. Must be a scale between -1.0 and 1.0.* + +.. option:: +scrolly= + + For ``vertical`` shape allows a scalar circular scroll of resulting y coordinates + to shift sections of the projection to the other vertical side of the map. + + *Defaults to 0.0. Must be a scale between -1.0 and 1.0.* + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/poly.rst b/assets/proj4/projections/poly.rst new file mode 100644 index 00000000..67815cca --- /dev/null +++ b/assets/proj4/projections/poly.rst @@ -0,0 +1,44 @@ +.. _poly: + +******************************************************************************** +Polyconic (American) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudoconical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | poly | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/poly.png + :width: 500 px + :align: center + :alt: Polyconic (American) + + proj-string: ``+proj=poly`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp1.rst b/assets/proj4/projections/putp1.rst new file mode 100644 index 00000000..e621a3c7 --- /dev/null +++ b/assets/proj4/projections/putp1.rst @@ -0,0 +1,42 @@ +.. _putp1: + +******************************************************************************** +Putnins P1 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp1.png + :width: 500 px + :align: center + :alt: Putnins P1 + + proj-string: ``+proj=putp1`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Putnins P1 projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp2.rst b/assets/proj4/projections/putp2.rst new file mode 100644 index 00000000..c0f0648e --- /dev/null +++ b/assets/proj4/projections/putp2.rst @@ -0,0 +1,42 @@ +.. _putp2: + +******************************************************************************** +Putnins P2 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp2.png + :width: 500 px + :align: center + :alt: Putnins P2 + + proj-string: ``+proj=putp2`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp3.rst b/assets/proj4/projections/putp3.rst new file mode 100644 index 00000000..17fd4012 --- /dev/null +++ b/assets/proj4/projections/putp3.rst @@ -0,0 +1,42 @@ +.. _putp3: + +******************************************************************************** +Putnins P3 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp3 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp3.png + :width: 500 px + :align: center + :alt: Putnins P3 + + proj-string: ``+proj=putp3`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp3p.rst b/assets/proj4/projections/putp3p.rst new file mode 100644 index 00000000..0e3aed3c --- /dev/null +++ b/assets/proj4/projections/putp3p.rst @@ -0,0 +1,42 @@ +.. _putp3p: + +******************************************************************************** +Putnins P3' +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp3p | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp3p.png + :width: 500 px + :align: center + :alt: Putnins P3' + + proj-string: ``+proj=putp3p`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp4p.rst b/assets/proj4/projections/putp4p.rst new file mode 100644 index 00000000..895c55e3 --- /dev/null +++ b/assets/proj4/projections/putp4p.rst @@ -0,0 +1,41 @@ +.. _putp4p: + +******************************************************************************** +Putnins P4' +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp4p | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/putp4p.png + :width: 500 px + :align: center + :alt: Putnins P4' + + proj-string: ``+proj=putp4p`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp5.rst b/assets/proj4/projections/putp5.rst new file mode 100644 index 00000000..511b570d --- /dev/null +++ b/assets/proj4/projections/putp5.rst @@ -0,0 +1,42 @@ +.. _putp5: + +******************************************************************************** +Putnins P5 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp5 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp5.png + :width: 500 px + :align: center + :alt: Putnins P5 + + proj-string: ``+proj=putp5`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp5p.rst b/assets/proj4/projections/putp5p.rst new file mode 100644 index 00000000..2150057d --- /dev/null +++ b/assets/proj4/projections/putp5p.rst @@ -0,0 +1,42 @@ +.. _putp5p: + +******************************************************************************** +Putnins P5' +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp5p | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp5p.png + :width: 500 px + :align: center + :alt: Putnins P5' + + proj-string: ``+proj=putp5p`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp6.rst b/assets/proj4/projections/putp6.rst new file mode 100644 index 00000000..1ef7718f --- /dev/null +++ b/assets/proj4/projections/putp6.rst @@ -0,0 +1,42 @@ +.. _putp6: + +******************************************************************************** +Putnins P6 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp6 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp6.png + :width: 500 px + :align: center + :alt: Putnins P6 + + proj-string: ``+proj=putp6`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/putp6p.rst b/assets/proj4/projections/putp6p.rst new file mode 100644 index 00000000..1976bfd0 --- /dev/null +++ b/assets/proj4/projections/putp6p.rst @@ -0,0 +1,42 @@ +.. _putp6p: + +******************************************************************************** +Putnins P6' +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | putp6p | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/putp6p.png + :width: 500 px + :align: center + :alt: Putnins P6' + + proj-string: ``+proj=putp6p`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/qsc.rst b/assets/proj4/projections/qsc.rst new file mode 100644 index 00000000..d7171fb4 --- /dev/null +++ b/assets/proj4/projections/qsc.rst @@ -0,0 +1,174 @@ +.. _qsc: + +******************************************************************************** +Quadrilateralized Spherical Cube +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | qsc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +The purpose of the Quadrilateralized Spherical Cube (QSC) projection is to project +a sphere surface onto the six sides of a cube: + +.. image:: ../../../images/qsc_concept.jpg + :width: 500 px + :align: center + :alt: Quadrilateralized Spherical Cube + +For this purpose, other alternatives can be used, notably :ref:`gnom` or +:ref:`healpix`. However, QSC projection has the following favorable properties: + +It is an equal-area projection, and at the same time introduces only limited angular +distortions. It treats all cube sides equally, i.e. it does not use different +projections for polar areas and equatorial areas. These properties make QSC +projection a good choice for planetary-scale terrain rendering. Map data can be +organized in quadtree structures for each cube side. See :cite:`LambersKolb2012` for an example. + +The QSC projection was introduced by :cite:`ONeilLaubscher1976`, +building on previous work by :cite:`ChanONeil1975`. For clarity: The +earlier QSC variant described in :cite:`ChanONeil1975` became known as the COBE QSC since it +was used by the NASA Cosmic Background Explorer (COBE) project; it is an approximately +equal-area projection and is not the same as the QSC projection. + +See also :cite:`CalabrettaGreisen2002` Sec. 5.6.2 and 5.6.3 for a description of both and +some analysis. + +In this implementation, the QSC projection projects onto one side of a circumscribed +cube. The cube side is selected by choosing one of the following six projection centers: + ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=0`` | front cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=90`` | right cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=180`` | back cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=-90`` | left cube side | ++-------------------------+--------------------+ +| ``+lat_0=90`` | top cube side | ++-------------------------+--------------------+ +| ``+lat_0=-90`` | bottom cube side | ++-------------------------+--------------------+ + +Furthermore, this implementation allows the projection to be applied to ellipsoids. +A preceding shift to a sphere is performed automatically; see :cite:`LambersKolb2012` for details. + + +Usage +############################################################################### + +The following example uses QSC projection via GDAL to create the six cube side +maps from a world map for the WGS84 ellipsoid:: + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=0 +lon_0=0" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff frontside.tiff + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=0 +lon_0=90" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff rightside.tiff + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=0 +lon_0=180" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff backside.tiff + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=0 +lon_0=-90" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff leftside.tiff + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=90 +lon_0=0" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff topside.tiff + + gdalwarp -t_srs "+wktext +proj=qsc +units=m +ellps=WGS84 +lat_0=-90 +lon_0=0" \ + -wo SOURCE_EXTRA=100 -wo SAMPLE_GRID=YES -te -6378137 -6378137 6378137 6378137 \ + worldmap.tiff bottomside.tiff + + +Explanation: + +* QSC projection is selected with ``+wktext +proj=qsc``. +* The WGS84 ellipsoid is specified with ``+ellps=WGS84``. +* The cube side is selected with ``+lat_0=... +lon_0=...``. +* The ``-wo`` options are necessary for GDAL to avoid holes in the output maps. +* The ``-te`` option limits the extends of the output map to the major axis diameter + (from -radius to +radius in both x and y direction). These are the dimensions of one side + of the circumscribing cube. + + +The resulting images can be laid out in a grid like below. + + +.. |topside| image:: ../../../images/qsc_topside.jpg + :scale: 50 % + :align: middle + :alt: Top side + +.. |leftside| image:: ../../../images/qsc_leftside.jpg + :scale: 50% + :align: middle + :alt: Left side + +.. |frontside| image:: ../../../images/qsc_frontside.jpg + :scale: 50% + :align: middle + :alt: Front side + +.. |rightside| image:: ../../../images/qsc_rightside.jpg + :scale: 50% + :align: middle + :alt: Right side + +.. |backside| image:: ../../../images/qsc_backside.jpg + :scale: 50% + :align: middle + :alt: Back side + +.. |bottomside| image:: ../../../images/qsc_bottomside.jpg + :scale: 50% + :align: middle + :alt: Bottom side + + ++------------+--------------+-------------+------------+ +| | |topside| | | | ++------------+--------------+-------------+------------+ +| |leftside| | |frontside| | |rightside| | |backside| | ++------------+--------------+-------------+------------+ +| | |bottomside| | | | ++------------+--------------+-------------+------------+ + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. `Wikipedia `_ +#. `NASA `_ diff --git a/assets/proj4/projections/qua_aut.rst b/assets/proj4/projections/qua_aut.rst new file mode 100644 index 00000000..67a5e5bf --- /dev/null +++ b/assets/proj4/projections/qua_aut.rst @@ -0,0 +1,42 @@ +.. _qua_aut: + +******************************************************************************** +Quartic Authalic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | qua_aut | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/qua_aut.png + :width: 500 px + :align: center + :alt: Quartic Authalic + + proj-string: ``+proj=qua_aut`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Quartic Authalic projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/rhealpix.rst b/assets/proj4/projections/rhealpix.rst new file mode 100644 index 00000000..b28f64b0 --- /dev/null +++ b/assets/proj4/projections/rhealpix.rst @@ -0,0 +1,75 @@ +.. _rhealpix: + +******************************************************************************** +rHEALPix +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | rhealpix | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. image:: ../../../images/rhealpix.png + :scale: 75% + :alt: rHEALPix + +rHEALPix is a projection based on the HEALPix projection. The implementation of +rHEALPix uses the HEALPix projection. The rHEALPix combines the peaks of the +HEALPix into a square. The square's position can be translated and rotated across +the x-axis which is a novel approach for the rHEALPix projection. The initial +intention of using rHEALPix in the Spatial Computation Engine Science Collaboration +Environment (SCENZGrid). + +Usage +############################################################################### + +To run a rHEALPix projection (on the default GRS80 ellipsoidal model,) use the following +command:: + + proj +proj=rhealpix +north_square=2 -E << EOF + > 55 12 + > EOF + 55 12 6115727.86 1553840.13 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. option:: +north_square + + Position of the north polar square. Valid inputs are 0--3. + + *Defaults to 0.0.* + +.. option:: +south_square + + Position of the south polar square. Valid inputs are 0--3. + + *Defaults to 0.0.* + + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. `NASA `_ +#. `Wikipedia `_ diff --git a/assets/proj4/projections/robin.rst b/assets/proj4/projections/robin.rst new file mode 100644 index 00000000..5ebdf5e5 --- /dev/null +++ b/assets/proj4/projections/robin.rst @@ -0,0 +1,42 @@ +.. _robin: + +******************************************************************************** +Robinson +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | robin | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/robin.png + :width: 500 px + :align: center + :alt: Robinson + + proj-string: ``+proj=robin`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/rouss.rst b/assets/proj4/projections/rouss.rst new file mode 100644 index 00000000..08fc6e36 --- /dev/null +++ b/assets/proj4/projections/rouss.rst @@ -0,0 +1,44 @@ +.. _rouss: + +******************************************************************************** +Roussilhe Stereographic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | rouss | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/rouss.png + :width: 500 px + :align: center + :alt: Roussilhe Stereographic + + proj-string: ``+proj=rouss`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/rpoly.rst b/assets/proj4/projections/rpoly.rst new file mode 100644 index 00000000..eee66c5e --- /dev/null +++ b/assets/proj4/projections/rpoly.rst @@ -0,0 +1,44 @@ +.. _rpoly: + +******************************************************************************** +Rectangular Polyconic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudoconical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | rpoly | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/rpoly.png + :width: 500 px + :align: center + :alt: Rectangular Polyconic + + proj-string: ``+proj=rpoly`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/s2.rst b/assets/proj4/projections/s2.rst new file mode 100644 index 00000000..e29ab65d --- /dev/null +++ b/assets/proj4/projections/s2.rst @@ -0,0 +1,120 @@ +.. _s2: + +******************************************************************************** +S2 +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | s2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. versionadded:: 8.2 + +The S2 projection, like the Quadrilateralized Spherical Cube (QSC) projection, projects +a sphere surface onto the six sides of a cube: + +.. image:: ../../../images/qsc_concept.jpg + :width: 500 px + :align: center + :alt: Quadrilateralized Spherical Cube + +S2 was created by Google to represent geographic data on the whole earth. The documentation can be found +at `S2 Geometry `_ It works by first +projecting a point on the sphere to a face of the cube. These are called u,v-coordinates, and they are in [-1,1] x [-1,1]. +This step is followed by a non-linear transformation to normalize the area of rectangles on the sphere. There are three +different choices available for this transformation, meaning that S2 is a family of projections. The final output is in +s,t-coordinates, which are in [0,1] x [0,1]. +See the comments in `S2 Code `_ +for an explanation of the tradeoff between speed and area-preservation. Note that the projection is azimuthal when "none" or +"linear" is selected for the area-normalization, but it is not azimuthal when "quadratic" or "tangent" is chosen. See +S2's `Earthcube page `_ +to visualize the unfolded cube and the orientation of each face. + +In this implementation, the cube side is selected by choosing one of the following six projection centers: + ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=0`` | front cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=90`` | right cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=180`` | back cube side | ++-------------------------+--------------------+ +| ``+lat_0=0 +lon_0=-90`` | left cube side | ++-------------------------+--------------------+ +| ``+lat_0=90`` | top cube side | ++-------------------------+--------------------+ +| ``+lat_0=-90`` | bottom cube side | ++-------------------------+--------------------+ + +The specific transformation can be chosen with the UVtoST parameter: + ++-------------------------+-----------------------------+ +| ``+UVtoST=linear`` | fastest, no normalization | ++-------------------------+-----------------------------+ +| ``+UVtoST=quadratic`` | fast, good normalization | ++-------------------------+-----------------------------+ +| ``+UVtoST=tangent`` | slowest, best normalization | ++-------------------------+-----------------------------+ +| ``+UVtoST=none`` | returns u,v-coordinates | ++-------------------------+-----------------------------+ + +Furthermore, this implementation allows the projection to be applied to ellipsoids. +A preceding shift to a sphere is performed automatically; see :cite:`LambersKolb2012` for details. +The output of the projection is in s,t-coordinates ([0,1] x [0,1]), so only the +eccentricity of the ellipse is taken into account: the absolute value of the axes does +not affect the output. + + +Usage +############################################################################### + +The following example uses S2 on the right face:: + + echo 90 0 | proj +proj=s2 +lat_0=0 +lon_0=90 +ellps=WGS84 +UVtoST=linear + + 0.5 0.5 + +Explanation: + +* S2 projection is selected with ``+proj=s2``. +* The WGS84 ellipsoid is specified with ``+ellps=WGS84``. +* The cube side is selected with ``+lat_0=... +lon_0=...``. +* The normalization transformation is selected with ``+UVtoST=...``. + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. option:: +UVtoST= + + The area-normalization transformation. Choose from {linear, quadratic, tangent, none} + + *Defaults to "quadratic".* + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +################################################################################ + +#. `S2's Website `_ diff --git a/assets/proj4/projections/sch.rst b/assets/proj4/projections/sch.rst new file mode 100644 index 00000000..f987d75d --- /dev/null +++ b/assets/proj4/projections/sch.rst @@ -0,0 +1,65 @@ +.. _sch: + +******************************************************************************** +Spherical Cross-track Height +******************************************************************************** ++---------------------+--------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+--------------------------------------------------------+ +| **Available forms** | Forward and inverse. | ++---------------------+--------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+--------------------------------------------------------+ +| **Alias** | sch | ++---------------------+--------------------------------------------------------+ +| **Domain** | 3D | ++---------------------+--------------------------------------------------------+ +| **Input type** | 3D coordinates | ++---------------------+--------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+--------------------------------------------------------+ + + proj-string: ``+proj=sch +plat_0=XX +plon_0=XX +phdg_0=XX`` + +The SCH coordinate system is a sensor aligned coordinate system developed at +JPL (Jet Propulsion Laboratory) for radar mapping missions. + +See :cite:`Hensley2002` + + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. option:: +plat_0= + + Peg latitude (in degree) + +.. option:: +plon_0= + + Peg longitude (in degree) + +.. option:: +phdg_0= + + Peg heading (in degree) + +Optional +------------------------------------------------------------------------------- + +.. option:: +h_0= + + Average height (in metre) + + *Defaults to 0.0.* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst diff --git a/assets/proj4/projections/sinu.rst b/assets/proj4/projections/sinu.rst new file mode 100644 index 00000000..6bd059fb --- /dev/null +++ b/assets/proj4/projections/sinu.rst @@ -0,0 +1,56 @@ +.. _sinu: + +******************************************************************************** +Sinusoidal (Sanson-Flamsteed) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | sinu | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/sinu.png + :width: 500 px + :align: center + :alt: Sinusoidal (Sanson-Flamsteed) + + proj-string: ``+proj=sinu`` + +MacBryde and Thomas developed generalized formulas for several of the +pseudocylindricals with sinusoidal meridians: + +.. math:: + + x = C\lambda(m+cos\theta) / ( m + 1) + +.. math:: + y = C\theta + +.. math:: + + C = \sqrt { (m + 1 ) / n } + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Sinusoidal projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/som.rst b/assets/proj4/projections/som.rst new file mode 100644 index 00000000..d3cf4377 --- /dev/null +++ b/assets/proj4/projections/som.rst @@ -0,0 +1,88 @@ +.. _som: + +******************************************************************************** +Space Oblique Mercator (SOM) +******************************************************************************** + +The Space Oblique Mercator (SOM) projection is a generalization of the Oblique +Mercator projection, itself an (oblique) aspect of the Mercator projection with +origins in the 16th century. In the previously available map projections, ground +tracks of satellites traced curved lines which complicated their use in +representing satellite remote sensing data. In response, SOM was specifically +designed to represent satellite ground tracks as straight lines. This had the +effect of minimizing distortions of data along a ground track and allowing +satellite data to be represented accurately as a rectangular array. + + + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conformal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | som | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/som.png + :width: 500 px + :align: center + :alt: Space Oblique Mercator (SOM) + + + proj-string: ``+proj=som +inc_angle=98.303820000243860022 +ps_rev=0.06866666666666667 +asc_lon=64.412896137498847793`` + + (gives same results as: ``+proj=som +inc_angle=1.7157253262878522r +ps_rev=0.06866666666666667 +asc_lon=1.1242171183417042r``) + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. option:: +asc_lon= + + Longitude of the ascending node for the orbit (decimal degrees). + +.. option:: +inc_angle= + + Inclination (vertical tilt) of the orbit with respect to the reference plane, measured at the ascending node (decimal degrees). + +.. option:: +ps_rev= + + Time required for a single orbit (days). + +.. note:: + + To specify asc_lon or inc_angle in radians (instead of degrees). Follow the value with the "r" character. + + As an example, to specify the ascending longitude as 1.1242171183417042 radians (~64.4129 decimal degrees): `+asc_lon=1.1242171183417042r`. + + For more information see :doc:`projections usage<../../usage/projections>`. + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Further reading +############### + +#. `Space Oblique Mercator Wikipedia `_ +#. `USGS Report on Space Oblique Mercator Mathematical Development `_ diff --git a/assets/proj4/projections/somerc.rst b/assets/proj4/projections/somerc.rst new file mode 100644 index 00000000..f8530d70 --- /dev/null +++ b/assets/proj4/projections/somerc.rst @@ -0,0 +1,30 @@ +.. _somerc: + +******************************************************************************** +Swiss Oblique Mercator +******************************************************************************** + +.. figure:: ./images/somerc.png + :width: 500 px + :align: center + :alt: Swiss Oblique Mercator + + proj-string: ``+proj=somerc`` + + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/stere.rst b/assets/proj4/projections/stere.rst new file mode 100644 index 00000000..1b82b159 --- /dev/null +++ b/assets/proj4/projections/stere.rst @@ -0,0 +1,62 @@ +.. _stere: + +******************************************************************************** +Stereographic +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | stere | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/stere.png + :width: 500 px + :align: center + :alt: Stereographic + + proj-string: ``+proj=stere +lat_0=90 +lat_ts=75`` + +.. note:: + + This projection method gives different results than the :ref:`sterea` + method in the non-polar cases (i.e. the oblique and equatorial case). The later + projection method is the one referenced by EPSG as "Oblique Stereographic". + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_0.rst + +.. option:: +lat_ts= + + Defines the latitude where scale is not distorted. It is only taken into + account for Polar Stereographic formulations (``+lat_0`` = +/- 90 ), and + then defaults to the ``+lat_0`` value. + If set to a value different from +/- 90, it takes precedence over ``+k_0`` + if both options are used together. + +.. include:: ../options/k_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/sterea.rst b/assets/proj4/projections/sterea.rst new file mode 100644 index 00000000..325be5b2 --- /dev/null +++ b/assets/proj4/projections/sterea.rst @@ -0,0 +1,53 @@ +.. _sterea: + +******************************************************************************** +Oblique Stereographic Alternative +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | sterea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/sterea.png + :width: 500 px + :align: center + :alt: Oblique Stereographic Alternative + + proj-string: ``+proj=sterea +lat_0=52.1561605555556 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel`` + +.. note:: + + This projection method, referenced by EPSG as "Oblique Stereographic", is + for example used for the Netherlands "Amersfoort / RD New" projected CRS. + It gives different results than the :ref:`stere` method in the non-polar cases + (i.e. the oblique and equatorial case). + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/tcc.rst b/assets/proj4/projections/tcc.rst new file mode 100644 index 00000000..3c5fe8eb --- /dev/null +++ b/assets/proj4/projections/tcc.rst @@ -0,0 +1,42 @@ +.. _tcc: + +******************************************************************************** +Transverse Central Cylindrical +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | tcc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/tcc.png + :width: 500 px + :align: center + :alt: Transverse Central Cylindrical + + proj-string: ``+proj=tcc`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/tcea.rst b/assets/proj4/projections/tcea.rst new file mode 100644 index 00000000..028e5c32 --- /dev/null +++ b/assets/proj4/projections/tcea.rst @@ -0,0 +1,44 @@ +.. _tcea: + +******************************************************************************** +Transverse Cylindrical Equal Area +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | tcea | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/tcea.png + :width: 500 px + :align: center + :alt: Transverse Cylindrical Equal Area + + proj-string: ``+proj=tcea`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/times.rst b/assets/proj4/projections/times.rst new file mode 100644 index 00000000..33e12148 --- /dev/null +++ b/assets/proj4/projections/times.rst @@ -0,0 +1,43 @@ +.. _times: + +******************************************************************************** +Times +******************************************************************************** + +See :cite:`Snyder1993`, p.213-214. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | times | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/times.png + :width: 500 px + :align: center + :alt: Times + + proj-string: ``+proj=times`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/tissot.rst b/assets/proj4/projections/tissot.rst new file mode 100644 index 00000000..814fa637 --- /dev/null +++ b/assets/proj4/projections/tissot.rst @@ -0,0 +1,33 @@ +.. _tissot: + +******************************************************************************** +Tissot +******************************************************************************** + +.. figure:: ./images/tissot.png + :width: 500 px + :align: center + :alt: Tissot + + proj-string: ``+proj=tissot +lat_1=60 +lat_2=65`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/tmerc.rst b/assets/proj4/projections/tmerc.rst new file mode 100644 index 00000000..879f3bf3 --- /dev/null +++ b/assets/proj4/projections/tmerc.rst @@ -0,0 +1,412 @@ +.. _tmerc: + +******************************************************************************** +Transverse Mercator +******************************************************************************** + +The transverse Mercator projection in its various forms is the most widely used projected coordinate system for world topographical and offshore mapping. +It is a conformal projection in which a chosen meridian projects to a +straight line at constant scale. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Transverse and oblique cylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical and ellipsoidal | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, with full accuracy within 3900 km | +| | of the central meridian | ++---------------------+----------------------------------------------------------+ +| **Alias** | tmerc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + + +.. figure:: ./images/tmerc.png + :width: 500 px + :align: center + :alt: Transverse Mercator + + proj-string: ``+proj=tmerc`` + +Usage +##### + + +Prior to the development of the Universal Transverse Mercator coordinate system, several European nations demonstrated the utility of grid-based conformal maps by mapping their territory during the interwar period. +Calculating the distance between two points on these maps could be performed more easily in the field (using the Pythagorean theorem) than was possible using the trigonometric formulas required under the graticule-based system of latitude and longitude. +In the post-war years, these concepts were extended into the Universal Transverse Mercator/Universal Polar Stereographic (UTM/UPS) coordinate system, which is a global (or universal) system of grid-based maps. + +The following table gives special cases of the Transverse Mercator projection. + ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| Projection Name | Areas | Central meridian | Zone width | Scale Factor | ++=====================================+=====================================================+================================+===========================================+==============+ +| Transverse Mercator | World wide | Various | less than 1000 km | Various | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| Transverse Mercator south oriented | Southern Africa | 2° intervals E of 11°E | 2° | 1.000 | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| UTM North hemisphere | World wide equator to 84°N | 6° intervals E & W of 3° E & W | Usually 6°, wider for Norway and Svalbard | 0.9996 | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| UTM South hemisphere | World wide north of 80°S to equator | 6° intervals E & W of 3° E & W | Always 6° | 0.9996 | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| Gauss-Kruger | Former USSR, Yugoslavia, Germany, S. America, China | Various, according to area | Usually less than 6°, often less than 4° | 1.0000 | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ +| Gauss Boaga | Italy | Various, according to area | 6° | 0.9996 | ++-------------------------------------+-----------------------------------------------------+--------------------------------+-------------------------------------------+--------------+ + + + +Example using Gauss-Kruger on Germany area (aka EPSG:31467) :: + + $ echo 9 51 | proj +proj=tmerc +lon_0=9 +x_0=3500000 +ellps=bessel + 3500000.00 5651505.56 + +Example using Gauss Boaga on Italy area (EPSG:3004) :: + + $ echo 15 42 | proj +proj=tmerc +lon_0=15 +k_0=0.9996 +x_0=2520000 +ellps=intl + 2520000.00 4649858.60 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. option:: +approx + + .. versionadded:: 6.0.0 + + Use the Evenden-Snyder algorithm described below under "Legacy + ellipsoidal form". It is faster than the default algorithm, but is + less accurate and diverges beyond 3° from the central meridian. + +.. option:: +algo=auto/evenden_snyder/poder_engsager + + .. versionadded:: 7.1 + + Selects the algorithm to use. The hardcoded value and the one defined in + :ref:`proj-ini` default to ``poder_engsager``; that is the most precise + one. + + When using auto, a heuristics based on the input coordinate to transform + is used to determine if the faster Evenden-Snyder method can be used, for + faster computation, without causing an error greater than 0.1 mm (for an + ellipsoid of the size of Earth) + + Note that :option:`+approx` and :option:`+algo` are mutually exclusive. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/k_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Mathematical definition +####################### + +The formulation given here for the Transverse Mercator projection is due +to Krüger :cite:`Krueger1912` who gave the series expansions accurate to +:math:`n^4`, where :math:`n = (a-b)/(a+b)` is the third flattening. +These series were extended to sixth order by Engsager and Poder in +:cite:`Poder1998` and :cite:`Engsager2007`. This gives full +double-precision accuracy within 3900 km of the central meridian (about +57% of the surface of the earth) :cite:`Karney2011tm`. The error is +less than 0.1 mm within 7000 km of the central meridian (about 89% of +the surface of the earth). + +This formulation consists of three steps: a conformal projection from +the ellipsoid to a sphere, the spherical transverse Mercator +projection, rectifying this projection to give constant scale on the +central meridian. + +The scale on the central meridian is :math:`k_0` and is set by ``+k_0``. + +Option :option:`+lon_0` sets the central meridian; in the formulation +below :math:`\lambda` is the longitude relative to the central meridian. + +Options :option:`+lat_0`, :option:`+x_0`, and :option:`+y_0` serve to +translate the projected coordinates so that at :math:`(\phi, \lambda) = +(\phi_0, \lambda_0)`, the projected coordinates are :math:`(x,y) = +(x_0,y_0)`. To simplify the formulas below, these options are set to +zero (their default values). + +Because the projection is conformal, the formulation is most +conveniently given in terms of complex numbers. In particular, the +unscaled projected coordinates :math:`\eta` (proportional to the +easting, :math:`x`) and :math:`\xi` (proportional to the northing, +:math:`y`) are combined into the single complex quantity :math:`\zeta = +\xi + i\eta`, where :math:`i=\sqrt{-1}`. Then any analytic function +:math:`f(\zeta)` defines a conformal mapping (this follows from the +Cauchy-Riemann conditions). + +Spherical form +************** + +Because the full (ellipsoidal) projection includes the spherical +projection as one of the components, we present the spherical form first +with the coordinates tagged with primes, :math:`\phi'`, +:math:`\lambda'`, :math:`\zeta' = \xi' + i\eta'`, :math:`x'`, +:math:`y'`, so that they can be distinguished from the corresponding +ellipsoidal coordinates (without the primes). The projected coordinates +for the sphere are given by + +.. math:: + + x' = k_0 R \eta';\qquad y' = k_0 R \xi' + +Forward projection +================== + +.. math:: + + \xi' = \tan^{-1}\biggl(\frac{\tan\phi'}{\cos\lambda'}\biggr) + +.. math:: + + \eta' = \sinh^{-1}\biggl(\frac{\sin\lambda'} + {\sqrt{\tan^2\phi' + \cos^2\lambda'}}\biggr) + + +Inverse projection +================== + +.. math:: + + \phi' = \tan^{-1}\biggl(\frac{\sin\xi'} + {\sqrt{\sinh^2\eta' + \cos^2\xi'}}\biggr) + +.. math:: + + \lambda' = \tan^{-1}\biggl(\frac{\sinh\eta'}{\cos\xi'}\biggr) + + +Ellipsoidal form +**************** + +The projected coordinates are given by + +.. math:: + + \zeta = \xi + i\eta;\qquad x = k_0 A \eta;\qquad y = k_0 A \xi + +.. math:: + + A = \frac a{1+n}\biggl(1 + \frac14 n^2 + \frac1{64} n^4 + + \frac1{256}n^6\biggr) + +The series for conversion between ellipsoidal and spherical geographic +coordinates and ellipsoidal and spherical projected coordinates are +given in matrix notation where :math:`\mathbf S(\theta)` and +:math:`\mathbf N` are the row and column vectors of length 6 + +.. math:: + + \mathbf S(\theta) = \begin{pmatrix} + \sin 2\theta & + \sin 4\theta & + \sin 6\theta & + \sin 8\theta & + \sin 10\theta & + \sin 12\theta + \end{pmatrix} + +.. math:: + + \mathbf N = \begin{pmatrix} + n \\ n^2 \\ n^3\\ n^4 \\ n^5 \\ n^6 + \end{pmatrix} + +and :math:`\mathsf C_{\alpha,\beta}` are upper triangular +:math:`6\times6` matrices. + +Relation between geographic coordinates +======================================= + +.. math:: + + \lambda' = \lambda + +.. math:: + + \phi' = \tan^{-1}\sinh\bigl(\sinh^{-1}\tan\phi + - e \tanh^{-1}(e\sin\phi)\bigr) + +Instead of using this analytical formula for :math:`\phi'`, the +conversions between :math:`\phi` and :math:`\phi'` use the series +approximations: + +.. math:: + + \phi' = \phi + \mathbf S(\phi) \cdot \mathsf C_{\chi,\phi} \cdot \mathbf N + +.. math:: + + \phi = \phi' + \mathbf S(\phi') \cdot \mathsf C_{\phi,\chi} \cdot \mathbf N + +.. math:: + + \mathsf C_{\chi,\phi} = \begin{pmatrix} + -2& \frac{2}{3}& \frac{4}{3}& -\frac{82}{45}& \frac{32}{45}& \frac{4642}{4725} \\ + & \frac{5}{3}& -\frac{16}{15}& -\frac{13}{9}& \frac{904}{315}& -\frac{1522}{945} \\ + & & -\frac{26}{15}& \frac{34}{21}& \frac{8}{5}& -\frac{12686}{2835} \\ + & & & \frac{1237}{630}& -\frac{12}{5}& -\frac{24832}{14175} \\ + & & & & -\frac{734}{315}& \frac{109598}{31185} \\ + & & & & & \frac{444337}{155925} \\ + \end{pmatrix} + +.. math:: + + \mathsf C_{\phi,\chi} = \begin{pmatrix} + 2& -\frac{2}{3}& -2& \frac{116}{45}& \frac{26}{45}& -\frac{2854}{675} \\ + & \frac{7}{3}& -\frac{8}{5}& -\frac{227}{45}& \frac{2704}{315}& \frac{2323}{945} \\ + & & \frac{56}{15}& -\frac{136}{35}& -\frac{1262}{105}& \frac{73814}{2835} \\ + & & & \frac{4279}{630}& -\frac{332}{35}& -\frac{399572}{14175} \\ + & & & & \frac{4174}{315}& -\frac{144838}{6237} \\ + & & & & & \frac{601676}{22275} \\ + \end{pmatrix} + +Here :math:`\phi'` is the conformal latitude (sometimes denoted by +:math:`\chi`) and :math:`\mathsf C_{\chi,\phi}` and :math:`\mathsf +C_{\phi,\chi}` are the coefficients in the trigonometric series for +converting between :math:`\phi` and :math:`\chi`. + +Relation between projected coordinates +====================================== + +.. math:: + + \zeta = \zeta' + \mathbf S(\zeta') \cdot \mathsf C_{\mu,\chi} \cdot \mathbf N + +.. math:: + + \zeta' = \zeta + \mathbf S(\zeta) \cdot \mathsf C_{\chi,\mu} \cdot \mathbf N + +.. math:: + + \mathsf C_{\mu,\chi} = \begin{pmatrix} + \frac{1}{2}& -\frac{2}{3}& \frac{5}{16}& \frac{41}{180}& -\frac{127}{288}& \frac{7891}{37800} \\ + & \frac{13}{48}& -\frac{3}{5}& \frac{557}{1440}& \frac{281}{630}& -\frac{1983433}{1935360} \\ + & & \frac{61}{240}& -\frac{103}{140}& \frac{15061}{26880}& \frac{167603}{181440} \\ + & & & \frac{49561}{161280}& -\frac{179}{168}& \frac{6601661}{7257600} \\ + & & & & \frac{34729}{80640}& -\frac{3418889}{1995840} \\ + & & & & & \frac{212378941}{319334400} \\ + \end{pmatrix} + +.. math:: + + \mathsf C_{\chi,\mu} = \begin{pmatrix} + -\frac{1}{2}& \frac{2}{3}& -\frac{37}{96}& \frac{1}{360}& \frac{81}{512}& -\frac{96199}{604800} \\ + & -\frac{1}{48}& -\frac{1}{15}& \frac{437}{1440}& -\frac{46}{105}& \frac{1118711}{3870720} \\ + & & -\frac{17}{480}& \frac{37}{840}& \frac{209}{4480}& -\frac{5569}{90720} \\ + & & & -\frac{4397}{161280}& \frac{11}{504}& \frac{830251}{7257600} \\ + & & & & -\frac{4583}{161280}& \frac{108847}{3991680} \\ + & & & & & -\frac{20648693}{638668800} \\ + \end{pmatrix} + +On the central meridian (:math:`\lambda = \lambda' = 0`), :math:`\zeta' += \phi'` is the conformal latitude :math:`\chi` and :math:`\zeta` plays +the role of the rectifying latitude (sometimes denoted by :math:`\mu`). +:math:`\mathsf C_{\mu,\chi}` and :math:`\mathsf C_{\chi,\mu}` are the +coefficients in the trigonometric series for converting between +:math:`\chi` and :math:`\mu`. + +Legacy ellipsoidal form +*********************** + +The formulas below describe the algorithm used when giving the +:option:`+approx` option. They are originally from :cite:`Snyder1987`, +but here quoted from :cite:`Evenden1995` and :cite:`Evenden2005`. These +are less accurate that the formulation above and are only valid within +about 5 degrees of the central meridian. Here :math:`M(\phi)` is the +meridional distance. + + +Forward projection +================== + +.. math:: + + N = \frac{k_0}{(1 - e^2 \sin^2\phi)^{1/2}} + +.. math:: + + R = \frac{k_0(1-e^2)}{(1-e^2 \sin^2\phi)^{3/2}} + +.. math:: + + t = \tan\phi + +.. math:: + + \eta = \frac{e^2}{1-e^2} \cos^2\phi + +.. math:: + + x &= k_0 \lambda \cos \phi \\ + &+ \frac{k_0 \lambda^3 \cos^3\phi}{3!}(1-t^2+\eta^2) \\ + &+ \frac{k_0 \lambda^5 \cos^5\phi}{5!}(5-18t^2+t^4+14\eta^2-58t^2\eta^2) \\ + &+\frac{k_0 \lambda^7 \cos^7\phi}{7!}(61-479t^2+179t^4-t^6) + +.. math:: + + y &= M(\phi) \\ + &+ \frac{k_0 \lambda^2 \sin\phi \cos \phi}{2!} \\ + &+ \frac{k_0 \lambda^4 \sin\phi \cos^3\phi}{4!}(5-t^2+9\eta^2+4\eta^4) \\ + &+ \frac{k_0 \lambda^6 \sin\phi \cos^5\phi}{6!}(61-58t^2+t^4+270\eta^2-330t^2\eta^2) \\ + &+ \frac{k_0 \lambda^8 \sin\phi \cos^7\phi}{8!}(1385-3111t^2+543t^4-t^6) + +Inverse projection +================== + +.. math:: + + \phi_1 = M^{-1}(y) + +.. math:: + + N_1 = \frac{k_0}{1 - e^2 \sin^2\phi_1)^{1/2}} + +.. math:: + + R_1 = \frac{k_0(1-e^2)}{(1-e^2 \sin^2\phi_1)^{3/2}} + +.. math:: + + t_1 = \tan(\phi_1) + +.. math:: + + \eta_1 = \frac{e^2}{1-e^2} \cos^2\phi_1 + +.. math:: + + \phi &= \phi_1 \\ + &- \frac{t_1 x^2}{2! R_1 N_1} \\ + &+ \frac{t_1 x^4}{4! R_1 N_1^3}(5+3t_1^2+\eta_1^2-4\eta_1^4-9\eta_1^2t_1^2) \\ + &- \frac{t_1 x^6}{6! R_1 N_1^5}(61+90t_1^2+46\eta_1^2+45t_1^4-252t_1^2\eta_1^2) \\ + &+ \frac{t_1 x^8}{8! R_1 N_1^7}(1385+3633t_1^2+4095t_1^4+1575t_1^6) + +.. math:: + + \lambda &= \frac{x}{\cos \phi N_1} \\ + &- \frac{x^3}{3! \cos \phi N_1^3}(1+2t_1^2+\eta_1^2) \\ + &+ \frac{x^5}{5! \cos \phi N_1^5}(5+6\eta_1^2+28t_1^2-3\eta_1^2+8t_1^2\eta_1^2) \\ + &- \frac{x^7}{7! \cos \phi N_1^7}(61+662t_1^2+1320t_1^4+720t_1^6) + +Further reading +############### + +#. `Wikipedia `_ diff --git a/assets/proj4/projections/tobmerc.rst b/assets/proj4/projections/tobmerc.rst new file mode 100644 index 00000000..7bc5cb95 --- /dev/null +++ b/assets/proj4/projections/tobmerc.rst @@ -0,0 +1,100 @@ +.. _tobmerc: + +******************************************************************************** +Tobler-Mercator +******************************************************************************** + +.. versionadded:: 6.0.0 + +Equal area cylindrical projection with the same latitudinal spacing as +Mercator projection. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical equal area | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical only | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global, conventionally truncated at about 80 degrees | +| | north and south | ++---------------------+----------------------------------------------------------+ +| **Alias** | tobmerc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ./images/tobmerc.png + :width: 500 px + :align: center + :alt: Tobler-Mercator + + proj-string: ``+proj=tobmerc`` + +Usage +##### + +The inappropriate use of the Mercator projection has declined but still +occasionally occurs. One method of contrasting the Mercator projection is to +present an alternative in the form of an equal area projection. The map +projection derived here is thus not simply a pretty Christmas tree ornament: +it is instead a complement to Mercator's conformal navigation anamorphosis +and can be displayed as an alternative. The equations for the new map +projection preserve the latitudinal stretching of the Mercator while +adjusting the longitudinal spacing. This allows placement of the new map +adjacent to that of Mercator. The surface area, while drastically warped, +maintains the correct magnitude. + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional. + +.. include:: ../options/k_0.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +.. include:: ../options/R.rst + +Mathematical definition +####################### + +The formulas describing the Tobler-Mercator are taken from Waldo Tobler's +article :cite:`Tobler2018` + +Spherical form +************** +For the spherical form of the projection we introduce the scaling factor: + +.. math:: + + k_0 = \cos^2 \phi_{ts} + +Forward projection +================== + +.. math:: + + x = k_0 \lambda + +.. math:: + + y = k_0 \ln \left[ \tan \left(\frac{\pi}{4} + \frac{\phi}{2} \right) \right] + + +Inverse projection +================== + +.. math:: + + \lambda = \frac{x}{k_0} + +.. math:: + + \phi = \frac{\pi}{2} - 2 \arctan \left[ e^{-y/k_0} \right] diff --git a/assets/proj4/projections/tpeqd.rst b/assets/proj4/projections/tpeqd.rst new file mode 100644 index 00000000..fb75a7b1 --- /dev/null +++ b/assets/proj4/projections/tpeqd.rst @@ -0,0 +1,56 @@ +.. _tpeqd: + +******************************************************************************** +Two Point Equidistant +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | tpeqd | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/tpeqd.png + :width: 500 px + :align: center + :alt: Two Point Equidistant + + proj-string: ``+proj=tpeqd +lat_1=60 +lat_2=65`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +lon_1= + + Longitude of first point. + +.. option:: +lat_1= + + Latitude of first point. + +.. option:: +lon_2= + + Longitude of second point. + +.. option:: +lat_2= + + Latitude of second point. + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/tpers.rst b/assets/proj4/projections/tpers.rst new file mode 100644 index 00000000..9dd73805 --- /dev/null +++ b/assets/proj4/projections/tpers.rst @@ -0,0 +1,70 @@ +.. _tpers: + +******************************************************************************** +Tilted perspective +******************************************************************************** ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | tpers | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + + +.. figure:: ./images/tpers.png + :width: 500 px + :align: center + :alt: Tilted perspective + + proj-string: ``+proj=tpers +h=5500000 +lat_0=40`` + +Tilted Perspective is similar to :ref:`nsper` (``nsper``) in that it simulates a +perspective view from a height. Where ``nsper`` projects onto a plane tangent to +the surface, Tilted Perspective orients the plane towards the direction of the +view. Thus, extra parameters specifying azimuth and tilt are required beyond +`nsper``'s ``h``. As with ``nsper``, ``lat_0`` & ``lon_0`` are +also required for satellite position. + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. include:: ../options/h.rst + +Optional +------------------------------------------------------------------------------- + +.. option:: +azi= + + Bearing in degrees away from north. + + *Defaults to 0.0.* + +.. option:: +tilt= + + Angle in degrees away from nadir. + + *Defaults to 0.0.* + +.. include:: ../options/lon_0.rst + +.. include:: ../options/lat_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + diff --git a/assets/proj4/projections/ups.rst b/assets/proj4/projections/ups.rst new file mode 100644 index 00000000..b652b563 --- /dev/null +++ b/assets/proj4/projections/ups.rst @@ -0,0 +1,31 @@ +.. _ups: + +******************************************************************************** +Universal Polar Stereographic +******************************************************************************** + +.. figure:: ./images/ups.png + :width: 500 px + :align: center + :alt: Universal Polar Stereographic + + proj-string: ``+proj=ups`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +south + + South polar aspect. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/urm5.rst b/assets/proj4/projections/urm5.rst new file mode 100644 index 00000000..4d2dd557 --- /dev/null +++ b/assets/proj4/projections/urm5.rst @@ -0,0 +1,41 @@ +.. _urm5: + +******************************************************************************** +Urmaev V +******************************************************************************** + +.. figure:: ./images/urm5.png + :width: 500 px + :align: center + :alt: Urmaev V + + proj-string: ``+proj=urm5 +n=0.9 +alpha=2 +q=4`` + +Parameters +################################################################################ + +Required parameters +-------------------------------------------------------------------------------- +.. option:: +n= + + Set the :math:`n` constant. Value between 0 and 1. + +Optional parameters +-------------------------------------------------------------------------------- +.. option:: +q= + + Set the :math:`q` constant. + +.. option:: +alpha= + + Set the :math:`\alpha` constant. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/ellps.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/urmfps.rst b/assets/proj4/projections/urmfps.rst new file mode 100644 index 00000000..b1f39b28 --- /dev/null +++ b/assets/proj4/projections/urmfps.rst @@ -0,0 +1,29 @@ +.. _urmfps: + +******************************************************************************** +Urmaev Flat-Polar Sinusoidal +******************************************************************************** + +.. figure:: ./images/urmfps.png + :width: 500 px + :align: center + :alt: Urmaev Flat-Polar Sinusoidal + + proj-string: ``+proj=urmfps +n=0.5`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. option:: +n= + + Set the :math:`n` constant. Value between 0 and 1. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/utm.rst b/assets/proj4/projections/utm.rst new file mode 100644 index 00000000..0777204f --- /dev/null +++ b/assets/proj4/projections/utm.rst @@ -0,0 +1,104 @@ +.. _utm: + +******************************************************************************** +Universal Transverse Mercator (UTM) +******************************************************************************** + +The Universal Transverse Mercator is a system of map projections divided into +sixty zones across the globe, with each zone corresponding to 6 degrees of +longitude. + ++---------------------+----------------------------------------------------------+ +| **Classification** | Transverse cylindrical, conformal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, ellipsoidal only | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Within the used zone, but transformations of coordinates | +| | in adjacent zones can be expected to be accurate as well | ++---------------------+----------------------------------------------------------+ +| **Alias** | utm | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + +.. figure:: ../../../images/utm_zones.png + :alt: Universal Transverse Mercator (UTM) zones + :figwidth: 100% + :align: center + + UTM zones. + +UTM projections are really the :ref:`Transverse Mercator` +to which specific parameters, such as central meridians, have been applied. +The Earth is divided into 60 zones each generally 6° wide in longitude. +Bounding meridians are evenly divisible by 6°, and zones are +numbered from 1 to 60 proceeding east from the 180th meridian from Greenwich +with minor exceptions :cite:`Snyder1987`. + +Usage +##### + +Convert geodetic coordinate to UTM Zone 32 on the northern hemisphere:: + + $ echo 12 56 | proj +proj=utm +zone=32 + 687071.44 6210141.33 + +Convert geodetic coordinate to UTM Zone 59 on the southern hemisphere:: + + $ echo 174 -44 | proj +proj=utm +zone=59 +south + 740526.32 5123750.87 + +Show the relationship of UTM to TM:: + + $ echo 121 24 | proj +proj=utm +lon_0=123 | proj -I +proj=tmerc +lon_0=123 +x_0=500000 +k=0.9996 + 121dE 24dN + +Parameters +################################################################################ + +Required +------------------------------------------------------------------------------- + +.. option:: +zone= + + Select which UTM zone to use. Can be a value between 1-60. + + +Optional +------------------------------------------------------------------------------- + +.. option:: +south + + Add this flag when using the UTM on the southern hemisphere. + +.. option:: +approx + + .. versionadded:: 6.0.0 + + Use faster, less accurate algorithm for the Transverse Mercator. + +.. option:: +algo=auto/evenden_snyder/poder_engsager + + .. versionadded:: 7.1 + + Selects the algorithm to use. The hardcoded value and the one defined in + :ref:`proj-ini` default to ``poder_engsager``, that is the most precise + one. + + When using auto, a heuristics based on the input coordinate to transform + is used to determine if the faster Evenden-Snyder method can be used, for + faster computation, without causing an error greater than 0.1 mm (for an + ellipsoid of the size of Earth) + + Note that :option:`+approx` and :option:`+algo` are mutually exclusive. + +.. include:: ../options/ellps.rst + +Further reading +############### + +#. `Wikipedia `_ diff --git a/assets/proj4/projections/vandg.rst b/assets/proj4/projections/vandg.rst new file mode 100644 index 00000000..32366534 --- /dev/null +++ b/assets/proj4/projections/vandg.rst @@ -0,0 +1,42 @@ +.. _vandg: + +******************************************************************************** +van der Grinten (I) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | vandg | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/vandg.png + :width: 500 px + :align: center + :alt: van der Grinten (I) + + proj-string: ``+proj=vandg`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/vandg2.rst b/assets/proj4/projections/vandg2.rst new file mode 100644 index 00000000..dee18774 --- /dev/null +++ b/assets/proj4/projections/vandg2.rst @@ -0,0 +1,42 @@ +.. _vandg2: + +******************************************************************************** +van der Grinten II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | vandg2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/vandg2.png + :width: 500 px + :align: center + :alt: van der Grinten II + + proj-string: ``+proj=vandg2`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/vandg3.rst b/assets/proj4/projections/vandg3.rst new file mode 100644 index 00000000..9f57f320 --- /dev/null +++ b/assets/proj4/projections/vandg3.rst @@ -0,0 +1,42 @@ +.. _vandg3: + +******************************************************************************** +van der Grinten III +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | vandg3 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/vandg3.png + :width: 500 px + :align: center + :alt: van der Grinten III + + proj-string: ``+proj=vandg3`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/vandg4.rst b/assets/proj4/projections/vandg4.rst new file mode 100644 index 00000000..e2215c91 --- /dev/null +++ b/assets/proj4/projections/vandg4.rst @@ -0,0 +1,42 @@ +.. _vandg4: + +******************************************************************************** +van der Grinten IV +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Miscellaneous | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | vandg4 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/vandg4.png + :width: 500 px + :align: center + :alt: van der Grinten IV + + proj-string: ``+proj=vandg4`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/vitk1.rst b/assets/proj4/projections/vitk1.rst new file mode 100644 index 00000000..c2baf2bf --- /dev/null +++ b/assets/proj4/projections/vitk1.rst @@ -0,0 +1,50 @@ +.. _vitk1: + +******************************************************************************** +Vitkovsky I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Conical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | vitk1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/vitk1.png + :width: 500 px + :align: center + :alt: Vitkovsky I + + proj-string: ``+proj=vitk1 +lat_1=45 +lat_2=55`` + +Parameters +################################################################################ + +Required +-------------------------------------------------------------------------------- + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lat_2.rst + +Optional +-------------------------------------------------------------------------------- + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag1.rst b/assets/proj4/projections/wag1.rst new file mode 100644 index 00000000..72c65ab2 --- /dev/null +++ b/assets/proj4/projections/wag1.rst @@ -0,0 +1,44 @@ +.. _wag1: + +******************************************************************************** +Wagner I (Kavrayskiy VI) +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag1.png + :width: 500 px + :align: center + :alt: Wagner I (Kavrayskiy VI) + + proj-string: ``+proj=wag1`` + +.. note:: This projection name may also be transliterated as Kavraisky VI. + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag2.rst b/assets/proj4/projections/wag2.rst new file mode 100644 index 00000000..60fa7789 --- /dev/null +++ b/assets/proj4/projections/wag2.rst @@ -0,0 +1,50 @@ +.. _wag2: + +******************************************************************************** +Wagner II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag2.png + :width: 500 px + :align: center + :alt: Wagner II + + proj-string: ``+proj=wag2`` + +.. math:: + + x &= 0.92483 \lambda \cos \theta + + y &= 1.38725\theta + + \sin \theta &= 0.88022 \sin(0.8855\phi) + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag3.rst b/assets/proj4/projections/wag3.rst new file mode 100644 index 00000000..6f3dcbb6 --- /dev/null +++ b/assets/proj4/projections/wag3.rst @@ -0,0 +1,50 @@ +.. _wag3: + +******************************************************************************** +Wagner III +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag3 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag3.png + :width: 500 px + :align: center + :alt: Wagner III + + proj-string: ``+proj=wag3`` + +.. math:: + + x &= [\cos\phi_{ts} / \cos ( 2\phi_{ts} / 3)] \lambda \cos (2\phi /3) + + y = \phi + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag4.rst b/assets/proj4/projections/wag4.rst new file mode 100644 index 00000000..a709acfa --- /dev/null +++ b/assets/proj4/projections/wag4.rst @@ -0,0 +1,42 @@ +.. _wag4: + +******************************************************************************** +Wagner IV +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag4 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag4.png + :width: 500 px + :align: center + :alt: Wagner IV + + proj-string: ``+proj=wag4`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag5.rst b/assets/proj4/projections/wag5.rst new file mode 100644 index 00000000..083b5ec6 --- /dev/null +++ b/assets/proj4/projections/wag5.rst @@ -0,0 +1,42 @@ +.. _wag5: + +******************************************************************************** +Wagner V +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag5 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag5.png + :width: 500 px + :align: center + :alt: Wagner V + + proj-string: ``+proj=wag5`` + +Parameters +################################################################################ + +.. note:: All parameters are optional. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag6.rst b/assets/proj4/projections/wag6.rst new file mode 100644 index 00000000..db7c7421 --- /dev/null +++ b/assets/proj4/projections/wag6.rst @@ -0,0 +1,42 @@ +.. _wag6: + +******************************************************************************** +Wagner VI +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag6 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag6.png + :width: 500 px + :align: center + :alt: Wagner VI + + proj-string: ``+proj=wag6`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the Wagner VI projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wag7.rst b/assets/proj4/projections/wag7.rst new file mode 100644 index 00000000..bf99d85b --- /dev/null +++ b/assets/proj4/projections/wag7.rst @@ -0,0 +1,29 @@ +.. _wag7: + +******************************************************************************** +Wagner VII +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Azimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wag7 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wag7.png + :width: 500 px + :align: center + :alt: Wagner VII + + proj-string: ``+proj=wag7`` diff --git a/assets/proj4/projections/webmerc.rst b/assets/proj4/projections/webmerc.rst new file mode 100644 index 00000000..55199b24 --- /dev/null +++ b/assets/proj4/projections/webmerc.rst @@ -0,0 +1,100 @@ +.. _webmerc: + +******************************************************************************** +Web Mercator / Pseudo Mercator +******************************************************************************** + +.. versionadded:: 5.1.0 + +The Web Mercator / Pseudo Mercator projection is a cylindrical map projection. +This is a variant of the regular :ref:`merc` projection, except that the computation +is done on a sphere, using the semi-major axis of the ellipsoid. + +From `Wikipedia `_: + + This projection is widely used by the Web Mercator, Google Web Mercator, + Spherical Mercator, WGS 84 Web Mercator[1] or WGS 84/Pseudo-Mercator is a + variant of the Mercator projection and is the de facto standard for Web + mapping applications. [...] + It is used by virtually all major online map providers [...] + Its official EPSG identifier is EPSG:3857, although others have been used + historically. + + ++---------------------+----------------------------------------------------------+ +| **Classification** | Cylindrical (non conformant if used with ellipsoid) | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | webmerc | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +Usage +######## + +Example:: + + $ echo 2 49 | proj +proj=webmerc +datum=WGS84 + 222638.98 6274861.39 + +Parameters +################################################################################ + +.. note:: All parameters for the projection are optional, except the ellipsoid + definition, which is WGS84 for the typical use case of EPSG:3857. + In which case, the other parameters are set to their default 0 value. + +.. include:: ../options/ellps.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst + +Mathematical definition +####################### + +The formulas describing the Mercator projection are all taken from G. Evenden's libproj manuals :cite:`Evenden2005`. + +Forward projection +================== + +.. math:: + + x = \lambda + +.. math:: + + y = \ln \left[ \tan \left(\frac{\pi}{4} + \frac{\phi}{2} \right) \right] + + +Inverse projection +================== + +.. math:: + + \lambda = {x} + +.. math:: + + \phi = \frac{\pi}{2} - 2 \arctan \left[ e^{-y} \right] + + + +Further reading +############### + +#. `Wikipedia `_ + + + diff --git a/assets/proj4/projections/weren.rst b/assets/proj4/projections/weren.rst new file mode 100644 index 00000000..6c7afdad --- /dev/null +++ b/assets/proj4/projections/weren.rst @@ -0,0 +1,42 @@ +.. _weren: + +******************************************************************************** +Werenskiold I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | weren | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/weren.png + :width: 500 px + :align: center + :alt: Werenskiold I + + proj-string: ``+proj=weren`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wink1.rst b/assets/proj4/projections/wink1.rst new file mode 100644 index 00000000..801bfad2 --- /dev/null +++ b/assets/proj4/projections/wink1.rst @@ -0,0 +1,44 @@ +.. _wink1: + +******************************************************************************** +Winkel I +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wink1 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wink1.png + :width: 500 px + :align: center + :alt: Winkel I + + proj-string: ``+proj=wink1`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wink2.rst b/assets/proj4/projections/wink2.rst new file mode 100644 index 00000000..059a7efa --- /dev/null +++ b/assets/proj4/projections/wink2.rst @@ -0,0 +1,44 @@ +.. _wink2: + +******************************************************************************** +Winkel II +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudocylindrical | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse, spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wink2 | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wink2.png + :width: 500 px + :align: center + :alt: Winkel II + + proj-string: ``+proj=wink2`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_ts.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/assets/proj4/projections/wintri.rst b/assets/proj4/projections/wintri.rst new file mode 100644 index 00000000..8ba28ccd --- /dev/null +++ b/assets/proj4/projections/wintri.rst @@ -0,0 +1,44 @@ +.. _wintri: + +******************************************************************************** +Winkel Tripel +******************************************************************************** + ++---------------------+----------------------------------------------------------+ +| **Classification** | Pseudoazimuthal | ++---------------------+----------------------------------------------------------+ +| **Available forms** | Forward and inverse spherical projection | ++---------------------+----------------------------------------------------------+ +| **Defined area** | Global | ++---------------------+----------------------------------------------------------+ +| **Alias** | wintri | ++---------------------+----------------------------------------------------------+ +| **Domain** | 2D | ++---------------------+----------------------------------------------------------+ +| **Input type** | Geodetic coordinates | ++---------------------+----------------------------------------------------------+ +| **Output type** | Projected coordinates | ++---------------------+----------------------------------------------------------+ + + +.. figure:: ./images/wintri.png + :width: 500 px + :align: center + :alt: Winkel Tripel + + proj-string: ``+proj=wintri`` + +Parameters +################################################################################ + +.. note:: All parameters are optional for the projection. + +.. include:: ../options/lat_1.rst + +.. include:: ../options/lon_0.rst + +.. include:: ../options/R.rst + +.. include:: ../options/x_0.rst + +.. include:: ../options/y_0.rst diff --git a/bun.lockb b/bun.lockb index a3acb2bd..9ca1859f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/readers/osm/fixtures/andorra-latest.osm.pbf b/fixtures/andorra-latest.osm.pbf similarity index 100% rename from src/readers/osm/fixtures/andorra-latest.osm.pbf rename to fixtures/andorra-latest.osm.pbf diff --git a/package.json b/package.json index 97bf0925..62cf87e5 100755 --- a/package.json +++ b/package.json @@ -62,28 +62,23 @@ "homepage": "https://github.com/Open-S2/s2-tools#readme", "devDependencies": { "@skypack/package-check": "^0.2.2", - "@types/bun": "^1.1.8", + "@types/bun": "^1.1.9", "@types/node": "^22.5.4", - "@types/pbf": "^3.0.5", - "@types/single-line-log": "^1.1.2", - "eslint": "^9.9.1", + "eslint": "^9.10.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^50.2.2", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-tsdoc": "^0.3.0", - "polygon-clipping": "^0.15.7", "prettier": "^3.3.3", - "typedoc": "^0.26.6", + "typedoc": "^0.26.7", "typedoc-plugin-coverage": "^3.3.0", - "typescript": "^5.5.4", - "typescript-eslint": "^8.4.0" + "typescript": "^5.6.2", + "typescript-eslint": "^8.5.0" }, "dependencies": { "earclip": "^1.1.0", - "gen-readlines": "^1.0.1", - "open-vector-tile": "^1.2.0", + "open-vector-tile": "^1.3.0", "s2-tilejson": "^1.6.0", - "s2json-spec": "^1.5.5", - "single-line-log": "^1.1.2" + "s2json-spec": "^1.5.5" } } diff --git a/src/db/README.md b/src/db/README.md new file mode 100644 index 00000000..9c8c7bfc --- /dev/null +++ b/src/db/README.md @@ -0,0 +1,3 @@ +# DataBases + +The value of the `kv` and `multimap` structures is that almost all GIS computations are done in two stages: write all then read. So once we get to the reading stage, the DB is essentially immutable. This allows a ton of optimization. diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 00000000..d71bc4d2 --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,2 @@ +export * from './kv'; +export * from './multimap'; diff --git a/src/db/kv/file.ts b/src/db/kv/file.ts new file mode 100644 index 00000000..4906462f --- /dev/null +++ b/src/db/kv/file.ts @@ -0,0 +1,88 @@ +import { closeSync, openSync, readSync, writeSync } from 'fs'; + +import type { KVStore, Key, Stringifiable } from '.'; + +/** + * NOTE: The File KVStore is designed to be used in states: + * - write-only. The initial state is write-only. Write all you need to before reading + * - read-only. Once you have written everything, the first read will lock the file to be static + * and read-only. + */ +export class KVFile implements KVStore { + state: 'read' | 'write' = 'read'; + // write params + #valueOffset = 0; + #keyWriteFd: number; + #valueWriteFd: number; + // read params + + /** + * Builds a new File based KV + * @param file - the path to the file + */ + constructor(file: string) { + this.#keyWriteFd = openSync(`${file}.keys`, 'a'); + this.#valueWriteFd = openSync(`${file}.values`, 'a'); + } + + /** + * Adds a value to be associated with a key + * @param key - the key + * @param value - the value to store + */ + set(key: K, value: V): void { + if (this.state !== 'read') throw new Error('Can no longer write to KVFile store.'); + // write key offset as a uint64 + const buffer = Buffer.alloc(8); + if (typeof key === 'number') { + buffer.writeUInt32LE(key & 0xffffffff, 0); + buffer.writeUInt32LE((key >>> 32) & 0xffffffff, 4); + } else { + const keyBuffer = Buffer.from( + (key as DataView).buffer, + (key as DataView).byteOffset, + (key as DataView).byteLength, + ); + keyBuffer.copy(buffer, 0, 0, 8); + } + buffer.writeUInt32LE(this.#valueOffset & 0xffffffff, 8); + buffer.writeUInt32LE((this.#valueOffset >>> 32) & 0xffffffff, 12); + writeSync(this.#keyWriteFd, buffer); + // write value and update value offset + const valueStr = JSON.stringify(value); + const valueBuf = Buffer.from(valueStr); + writeSync(this.#valueWriteFd, valueBuf); + this.#valueOffset += valueBuf.byteLength; + } + + /** + * Gets the value associated with a key + * @param key - the key + * @returns the value if the map contains values for the key + */ + get(key: K): V | undefined { + this.#switchToWriteState(); + return undefined; + } + + /** + * Check if the map contains the key + * @param key - the key + * @returns true if the map contains value(s) for the key + */ + has(key: K): boolean { + this.#switchToWriteState(); + return false; + } + + /** Switches to write state if in read. Also sort the keys. */ + #switchToWriteState(): void { + if (this.state === 'write') return; + if (this.state === 'read') { + this.state = 'write'; + closeSync(this.#keyWriteFd); + closeSync(this.#valueWriteFd); + // TODO: SORT KEYS + } + } +} diff --git a/src/db/kv/index.ts b/src/db/kv/index.ts new file mode 100644 index 00000000..63d11e5c --- /dev/null +++ b/src/db/kv/index.ts @@ -0,0 +1,17 @@ +/** Always a uint64. Sometimes a number is enough though */ +export type Key = number | DataView; + +/** Any type of value that can be stored as a string */ +export type Stringifiable = string | object | number | boolean | null; + +/** + * Represents a key-value store + */ +export interface KVStore { + get: (key: K) => V | undefined; + set: (key: K, value: V) => void; + has: (key: K) => boolean; +} + +/** Just a placeholder to explain what a local key-value store essentially is */ +export class KV extends Map implements KVStore {} diff --git a/src/db/multimap/file.ts b/src/db/multimap/file.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/db/multimap/index.ts b/src/db/multimap/index.ts new file mode 100644 index 00000000..10c1ee49 --- /dev/null +++ b/src/db/multimap/index.ts @@ -0,0 +1,50 @@ +import type { Key, Stringifiable } from '../kv'; + +/** Represents a key-value store */ +export interface MultiMapStore { + get: (key: K) => V[] | undefined; + set: (key: K, value: V) => void; + has: (key: K) => boolean; +} + +/** A local multimap key-value store */ +export class MultiMap implements MultiMapStore { + private map: Map; + + /** Builds a new MultiMap */ + constructor() { + this.map = new Map(); + } + + /** + * Adds a value to the list of values associated with a key + * @param key - the key + * @param value - the value to store + */ + set(key: K, value: V): void { + const list = this.get(key); + if (list === undefined) { + this.map.set(key, [value]); + } else { + list.push(value); + } + } + + /** + * Gets the list of values associated with a key + * @param key - the key + * @returns the list of values if the map contains values for the key + */ + get(key: K): V[] | undefined { + return this.map.get(key); + } + + /** + * Check if the map contains the key + * @param key - the key + * @returns true if the map contains value(s) for the key + */ + has(key: K): boolean { + return this.map.has(key); + } +} diff --git a/src/geometry/index.ts b/src/geometry/index.ts index 893d5216..4bfe9a8e 100644 --- a/src/geometry/index.ts +++ b/src/geometry/index.ts @@ -40,10 +40,13 @@ export interface BaseFeatureCollection bbox?: BBOX; } /** WG FeatureCollection */ -export type FeatureCollection = BaseFeatureCollection<'FeatureCollection', Feature | VectorFeature>; +export type FeatureCollection> = BaseFeatureCollection< + 'FeatureCollection', + Feature | VectorFeature +>; /** S2 FeatureCollection */ -export interface S2FeatureCollection - extends BaseFeatureCollection<'S2FeatureCollection', S2Feature> { +export interface S2FeatureCollection> + extends BaseFeatureCollection<'S2FeatureCollection', S2Feature> { faces: Face[]; } @@ -56,29 +59,34 @@ export interface BaseFeature< T = FeatureType, P extends Properties = Properties, G = Geometry | VectorGeometry, + M = Record, > { type: T; id?: number; face?: Face; properties: P; geometry: G; - metadata?: Record; + metadata?: M; } /** WG Feature */ export type Feature< + M = Record, P extends Properties = Properties, - M extends MValue = MValue, - G = Geometry, -> = BaseFeature<'Feature', P, G>; + D extends MValue = MValue, + G = Geometry, +> = BaseFeature<'Feature', P, G, M>; /** WG Vector Feature */ -export type VectorFeature

= BaseFeature< - 'VectorFeature', - P, - G ->; +export type VectorFeature< + M = Record, + P extends Properties = Properties, + G = VectorGeometry, +> = BaseFeature<'VectorFeature', P, G, M>; /** S2 Feature */ -export interface S2Feature

- extends BaseFeature<'S2Feature', P, G> { +export interface S2Feature< + M = Record, + P extends Properties = Properties, + G = VectorGeometry, +> extends BaseFeature<'S2Feature', P, G, M> { face: Face; } diff --git a/src/proj4/Point.js b/src/proj4/Point.js deleted file mode 100644 index 27cfc111..00000000 --- a/src/proj4/Point.js +++ /dev/null @@ -1,64 +0,0 @@ -import {toPoint, forward} from 'mgrs'; - -function Point(x, y, z) { - if (!(this instanceof Point)) { - return new Point(x, y, z); - } - if (Array.isArray(x)) { - this.x = x[0]; - this.y = x[1]; - this.z = x[2] || 0.0; - } else if(typeof x === 'object') { - this.x = x.x; - this.y = x.y; - this.z = x.z || 0.0; - } else if (typeof x === 'string' && typeof y === 'undefined') { - var coords = x.split(','); - this.x = parseFloat(coords[0], 10); - this.y = parseFloat(coords[1], 10); - this.z = parseFloat(coords[2], 10) || 0.0; - } else { - this.x = x; - this.y = y; - this.z = z || 0.0; - } - console.warn('proj4.Point will be removed in version 3, use proj4.toPoint'); -} - -Point.fromMGRS = function(mgrsStr) { - return new Point(toPoint(mgrsStr)); -}; -Point.prototype.toMGRS = function(accuracy) { - return forward([this.x, this.y], accuracy); -}; -export default Point; - -export function sanityCheck(point) { - checkCoord(point.x); - checkCoord(point.y); -} -function checkCoord(num) { - if (typeof Number.isFinite === 'function') { - if (Number.isFinite(num)) { - return; - } - throw new TypeError('coordinates must be finite numbers'); - } - if (typeof num !== 'number' || num !== num || !isFinite(num)) { - throw new TypeError('coordinates must be finite numbers'); - } -} - -// export function toPoint (array){ -// var out = { -// x: array[0], -// y: array[1] -// }; -// if (array.length>2) { -// out.z = array[2]; -// } -// if (array.length>3) { -// out.m = array[3]; -// } -// return out; -// } \ No newline at end of file diff --git a/src/proj4/Proj.js b/src/proj4/Proj.js index 7828c22a..18e433fe 100644 --- a/src/proj4/Proj.js +++ b/src/proj4/Proj.js @@ -1,10 +1,9 @@ import parseCode from './parseCode'; -import extend from './extend'; import projections from './projections'; -import {sphere as dc_sphere, eccentricity as dc_eccentricity} from './deriveConstants'; +import {sphere as dc_sphere, eccentricity as dc_eccentricity} from './constants/derives'; import Datum from './constants/Datum'; import datum from './datum'; -import match from './match'; +import match from './util/match'; import {getNadgrids} from "./nadgrid"; function Projection(srsCode,callback) { @@ -72,3 +71,19 @@ function Projection(srsCode,callback) { Projection.projections = projections; Projection.projections.start(); export default Projection; + + +function extend (destination, source) { + destination = destination || {}; + var value, property; + if (!source) { + return destination; + } + for (property in source) { + value = source[property]; + if (value !== undefined) { + destination[property] = value; + } + } + return destination; +} diff --git a/src/proj4/README.md b/src/proj4/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/proj4/adjust_axis.js b/src/proj4/adjust_axis.js deleted file mode 100644 index f36c895c..00000000 --- a/src/proj4/adjust_axis.js +++ /dev/null @@ -1,61 +0,0 @@ -export default function(crs, denorm, point) { - var xin = point.x, - yin = point.y, - zin = point.z || 0.0; - var v, t, i; - var out = {}; - for (i = 0; i < 3; i++) { - if (denorm && i === 2 && point.z === undefined) { - continue; - } - if (i === 0) { - v = xin; - if ("ew".indexOf(crs.axis[i]) !== -1) { - t = 'x'; - } else { - t = 'y'; - } - - } - else if (i === 1) { - v = yin; - if ("ns".indexOf(crs.axis[i]) !== -1) { - t = 'y'; - } else { - t = 'x'; - } - } - else { - v = zin; - t = 'z'; - } - switch (crs.axis[i]) { - case 'e': - out[t] = v; - break; - case 'w': - out[t] = -v; - break; - case 'n': - out[t] = v; - break; - case 's': - out[t] = -v; - break; - case 'u': - if (point[t] !== undefined) { - out.z = v; - } - break; - case 'd': - if (point[t] !== undefined) { - out.z = -v; - } - break; - default: - //console.log("ERROR: unknow axis ("+crs.axis[i]+") - check definition of "+crs.projName); - return null; - } - } - return out; -} diff --git a/src/proj4/constants/derives.ts b/src/proj4/constants/derives.ts new file mode 100644 index 00000000..9ec61983 --- /dev/null +++ b/src/proj4/constants/derives.ts @@ -0,0 +1,84 @@ +import { EPSLN, RA4, RA6, SIXTH } from './values'; +import ellipsoids, { WGS84 } from './ellipsoid'; + +import type { Ellipsoid } from './ellipsoid'; + +import match from '../util/match'; + +/** Describes an ellipsoid's eccentricity */ +export interface Eccentricity { + es: number; + e: number; + ep2: number; +} + +/** + * @param ellipsoid - ellipsoid + * @param RA - true if apply radius of curvature correction + * @returns - eccentricity + */ +export function eccentricity(ellipsoid: Ellipsoid, RA = false): Eccentricity { + let { a } = ellipsoid; + const { b } = ellipsoid; + let a2 = a * a; // used in geocentric + const b2 = b * b; // used in geocentric + let es = (a2 - b2) / a2; // e ^ 2 + let e = 0; + if (RA) { + a *= 1 - es * (SIXTH + es * (RA4 + es * RA6)); + a2 = a * a; + es = 0; + } else { + e = Math.sqrt(es); // eccentricity + } + const ep2 = (a2 - b2) / b2; // used in geocentric + return { es: es, e: e, ep2: ep2 }; +} + +/** Describes a sphere's eccentricity and if it is a true sphere or not */ +export interface Sphere { + a: number; + b: number; + rf: number; + sphere: boolean; +} + +/** + * Builds a sphere with ellipsoid parameters + * @param ellps - name of ellipsoid + * @param a - semi-major axis + * @param b - semi-minor axis + * @param rf - inverse flattening + * @param sphere - true if ellipsoid is a true sphere + * @returns - Sphere with ellipsoid parameters + */ +export function sphere( + ellps?: string, + a?: number, + b?: number, + rf?: number, + sphere = false, +): Sphere { + if (a === undefined) { + // do we have an ellipsoid? + let ellipse = match(ellipsoids, ellps); + if (ellipse === undefined) ellipse = WGS84; + a = ellipse.a; + b = ellipse.b; + rf = ellipse.rf; + } + if (rf && b === undefined) { + b = (1.0 - 1.0 / rf) * a; + } else { + b = a; + } + if (rf === undefined) { + rf = (a - b) / a; + } + if (rf === 0 || Math.abs(a - b) < EPSLN) { + sphere = true; + b = a; + } + + return { a, b, rf, sphere }; +} diff --git a/src/proj4/constants/ellipsoid.ts b/src/proj4/constants/ellipsoid.ts index 30c3c937..37bc7341 100644 --- a/src/proj4/constants/ellipsoid.ts +++ b/src/proj4/constants/ellipsoid.ts @@ -1,97 +1,115 @@ /** ellipsoid constants */ export interface Ellipsoid { + /** semi-major axis */ a: number; - b?: number; - rf?: number; + /** semi-minor axis */ + b: number; + /** inverse flattening */ + rf: number; } /** MERIT 1983 */ export const MERIT: Ellipsoid = { - a: 6378137.0, + a: 6_378_137.0, + b: 6_356_752.31, rf: 298.257, }; /** Soviet Geodetic System 85 */ export const SGS85: Ellipsoid = { - a: 6378136.0, + a: 6_378_136.0, + b: 6_356_751.99, rf: 298.257, }; /** GRS 1980(IUGG, 1980) */ export const GRS80: Ellipsoid = { - a: 6378137.0, + a: 6_378_137.0, + b: 6_356_752.3141, rf: 298.257222101, }; /** IAU 1976 */ export const IAU76: Ellipsoid = { - a: 6378140.0, + a: 6_378_140.0, + b: 6_356_755.29, rf: 298.257, }; /** Airy 1830 */ export const airy: Ellipsoid = { - a: 6377563.396, - b: 6356256.91, + a: 6_377_563.396, + b: 6_356_256.91, + rf: 299.325, }; /** Appl. Physics. 1965 */ export const APL4: Ellipsoid = { - a: 6378137, + a: 6_378_137, + b: 6_356_752.31, rf: 298.25, }; /** Naval Weapons Lab., 1965 */ export const NWL9D: Ellipsoid = { - a: 6378145.0, + a: 6_378_145.0, + b: 6_356_760.298, rf: 298.25, }; /** Modified Airy */ export const mod_airy: Ellipsoid = { - a: 6377340.189, - b: 6356034.446, + a: 6_377_340.189, + b: 6_356_034.446, + rf: 299.325, }; /** Andrae 1876 (Den., Iclnd.) */ export const andrae: Ellipsoid = { - a: 6377104.43, + a: 6_377_104.43, + b: 6_356_913.06, rf: 300.0, }; /** Australian Natl & S. Amer. 1969 */ export const aust_SA: Ellipsoid = { - a: 6378160.0, + a: 6_378_160.0, + b: 6_356_752.31, rf: 298.25, }; /** GRS 67(IUGG 1967) */ export const GRS67: Ellipsoid = { - a: 6378160.0, + a: 6_378_160.0, + b: 6_356_750.197, rf: 298.247167427, }; /** Bessel 1841 */ export const bessel: Ellipsoid = { - a: 6377397.155, + a: 6_377_397.155, + b: 6_356_078.24, rf: 299.1528128, }; /** Bessel 1841 (Namibia) */ export const bess_nam: Ellipsoid = { - a: 6377483.865, + a: 6_377_483.865, + b: 6_356_292.65, rf: 299.1528128, }; /** Clarke 1866 */ export const clrk66: Ellipsoid = { - a: 6378206.4, - b: 6356583.8, + a: 6_378_206.4, + b: 6_356_583.8, + rf: 294.979, }; /** Clarke 1880 mod. */ export const clrk80: Ellipsoid = { - a: 6378249.145, + a: 6_378_249.145, + b: 6_356_592.34, rf: 293.4663, }; @@ -104,167 +122,245 @@ export const clrk80ign: Ellipsoid = { /** Clarke 1858 */ export const clrk58: Ellipsoid = { - a: 6378293.645208759, + a: 6_378_293.645208759, + b: 6_356_670.51, rf: 294.2606763692654, }; /** Comm. des Poids et Mesures 1799 */ export const CPM: Ellipsoid = { - a: 6375738.7, + a: 6_375_738.7, + b: 6_356_204.15, rf: 334.29, }; /** Delambre 1810 (Belgium) */ export const delmbr: Ellipsoid = { - a: 6376428.0, + a: 6_376_428.0, + b: 6_356_225.95, rf: 311.5, }; /** Engelis 1985 */ export const engelis: Ellipsoid = { - a: 6378136.05, + a: 6_378_136.05, + b: 6_356_752.27, rf: 298.2566, }; -/**Everest 1830 (Angola) */ +/** Everest 1830 (Angola) */ export const evrst30: Ellipsoid = { - a: 6377276.345, + a: 6_377_276.345, + b: 6_356_626.27, rf: 300.8017, }; -/**Everest 1948 */ +/** Everest 1948 */ export const evrst48: Ellipsoid = { - a: 6377304.063, + a: 6_377_304.063, + b: 6_356_622.48, rf: 300.8017, }; -/**Everest 1956 */ +/** Everest 1956 */ export const evrst56: Ellipsoid = { - a: 6377301.243, + a: 6_377_301.243, + b: 6_356_622.78, rf: 300.8017, }; -/**Everest 1969 */ +/** Everest 1969 */ export const evrst69: Ellipsoid = { - a: 6377295.664, + a: 6_377_295.664, + b: 6_356_628.28, rf: 300.8017, }; /** Everest (Sabah & Sarawak) */ export const evrstSS: Ellipsoid = { - a: 6377298.556, + a: 6_377_298.556, + b: 6_356_628.91, rf: 300.8017, }; /** Fischer (Mercury Datum) 1960 */ export const fschr60: Ellipsoid = { - a: 6378166.0, + a: 6_378_166.0, + b: 6_356_741.84, rf: 298.3, }; /** Fischer 1960 */ export const fschr60m: Ellipsoid = { - a: 6378155.0, + a: 6_378_155.0, + b: 6_356_733.14, rf: 298.3, }; /** Fischer 1968 */ export const fschr68: Ellipsoid = { - a: 6378150.0, + a: 6_378_150.0, + b: 6_356_728.78, rf: 298.3, }; /** Helmert 1906 */ export const helmert: Ellipsoid = { - a: 6378200.0, + a: 6_378_200.0, + b: 6_356_730.91, rf: 298.3, }; /** Hough */ export const hough: Ellipsoid = { - a: 6378270.0, + a: 6_378_270.0, + b: 6_356_735.96, rf: 297.0, }; /** International 1909 (Hayford) */ export const intl: Ellipsoid = { - a: 6378388.0, + a: 6_378_388.0, + b: 6_356_847.3, rf: 297.0, }; /** Kaula 1961 */ export const kaula: Ellipsoid = { - a: 6378163.0, + a: 6_378_163.0, + b: 6_356_751.95, rf: 298.24, }; /** Lerch 1979 */ export const lerch: Ellipsoid = { - a: 6378139.0, + a: 6_378_139.0, + b: 6_356_751.82, rf: 298.257, }; /** Maupertius 1738 */ export const mprts: Ellipsoid = { - a: 6397300.0, + a: 6_397_300.0, + b: 6_365_965.29, rf: 191.0, }; /** New International 1967 */ export const new_intl: Ellipsoid = { - a: 6378157.5, - b: 6356772.2, + a: 6_378_157.5, + b: 6_356_772.2, + rf: 298.255, }; /** Plessis 1817 (France) */ export const plessis: Ellipsoid = { - a: 6376523.0, - rf: 6355863.0, + a: 6_376_523.0, + b: 6_355_863.0, + rf: 308.533, }; /** Krassovsky, 1942 */ export const krass: Ellipsoid = { - a: 6378245.0, + a: 6_378_245.0, + b: 6_356_738.58, rf: 298.3, }; /** Southeast Asia */ export const SEasia: Ellipsoid = { - a: 6378155.0, - b: 6356773.3205, + a: 6_378_155.0, + b: 6_356_773.3205, + rf: 298.257, }; /** Walbeck */ export const walbeck: Ellipsoid = { - a: 6376896.0, - b: 6355834.8467, + a: 6_376_896.0, + b: 6_355_834.8467, + rf: 302.081, }; /** WGS 60 */ export const WGS60: Ellipsoid = { - a: 6378165.0, + a: 6_378_165.0, + b: 6_356_748.16, rf: 298.3, }; +/** WGS 66 */ export const WGS66: Ellipsoid = { - a: 6378145.0, + a: 6_378_145.0, + b: 6_356_746.7, rf: 298.25, }; /** WGS 72 */ export const WGS7: Ellipsoid = { - a: 6378135.0, + a: 6_378_135.0, + b: 6_356_756.81, rf: 298.26, }; /** WGS 84 */ export const WGS84: Ellipsoid = { - a: 6378137.0, + a: 6_378_137.0, + b: 6_356_752.314, rf: 298.257223563, }; /** Normal Sphere (r=6370997) */ -export const sphere: Ellipsoid = { +export const SPHERE: Ellipsoid = { a: 6370997.0, b: 6370997.0, -}; + rf: Infinity, // Indicates a sphere with no flattening +}; + +const ellipsoids: Record = { + MERIT, + SGS85, + GRS80, + IAU76, + airy, + APL4, + NWL9D, + mod_airy, + andrae, + aust_SA, + GRS67, + bessel, + bess_nam, + clrk66, + clrk80, + clrk80ign, + clrk58, + CPM, + delmbr, + engelis, + evrst30, + evrst48, + evrst56, + evrst69, + evrstSS, + fschr60, + fschr60m, + fschr68, + helmert, + hough, + intl, + kaula, + lerch, + mprts, + new_intl, + plessis, + krass, + SEasia, + walbeck, + WGS60, + WGS66, + WGS7, + WGS84, + SPHERE, +}; + +export default ellipsoids; diff --git a/src/proj4/constants/index.ts b/src/proj4/constants/index.ts index 44bafdcd..6f391045 100644 --- a/src/proj4/constants/index.ts +++ b/src/proj4/constants/index.ts @@ -1,4 +1,5 @@ export * from './datum'; +export * from './derives'; export * from './ellipsoid'; export * from './primeMeridian'; export * from './values'; diff --git a/src/proj4/constants/values.ts b/src/proj4/constants/values.ts index 18f4ad68..42e60124 100644 --- a/src/proj4/constants/values.ts +++ b/src/proj4/constants/values.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-loss-of-precision */ export const PJD_3PARAM = 1; export const PJD_7PARAM = 2; export const PJD_GRIDSHIFT = 3; diff --git a/src/proj4/datum.js b/src/proj4/datum.js deleted file mode 100644 index 9d82f526..00000000 --- a/src/proj4/datum.js +++ /dev/null @@ -1,39 +0,0 @@ -import {PJD_3PARAM, PJD_7PARAM, PJD_GRIDSHIFT, PJD_WGS84, PJD_NODATUM, SEC_TO_RAD} from './constants/values'; - -function datum(datumCode, datum_params, a, b, es, ep2, nadgrids) { - var out = {}; - - if (datumCode === undefined || datumCode === 'none') { - out.datum_type = PJD_NODATUM; - } else { - out.datum_type = PJD_WGS84; - } - - if (datum_params) { - out.datum_params = datum_params.map(parseFloat); - if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) { - out.datum_type = PJD_3PARAM; - } - if (out.datum_params.length > 3) { - if (out.datum_params[3] !== 0 || out.datum_params[4] !== 0 || out.datum_params[5] !== 0 || out.datum_params[6] !== 0) { - out.datum_type = PJD_7PARAM; - out.datum_params[3] *= SEC_TO_RAD; - out.datum_params[4] *= SEC_TO_RAD; - out.datum_params[5] *= SEC_TO_RAD; - out.datum_params[6] = (out.datum_params[6] / 1000000.0) + 1.0; - } - } - } - - if (nadgrids) { - out.datum_type = PJD_GRIDSHIFT; - out.grids = nadgrids; - } - out.a = a; //datum object also uses these values - out.b = b; - out.es = es; - out.ep2 = ep2; - return out; -} - -export default datum; diff --git a/src/proj4/datum.ts b/src/proj4/datum.ts new file mode 100644 index 00000000..a9921f29 --- /dev/null +++ b/src/proj4/datum.ts @@ -0,0 +1,552 @@ +import { adjustLon } from './common'; +import { + HALF_PI, + PJD_3PARAM, + PJD_7PARAM, + PJD_GRIDSHIFT, + PJD_NODATUM, + PJD_WGS84, + R2D, + SEC_TO_RAD, + SRS_WGS84_ESQUARED, + SRS_WGS84_SEMIMAJOR, + SRS_WGS84_SEMIMINOR, +} from './constants'; + +import type { VectorPoint } from 's2-tools/geometry'; + +/** + * @param datumCode + * @param datum_params + * @param a + * @param b + * @param es + * @param ep2 + * @param nadgrids + */ +export function buildDatum(datumCode, datum_params, a, b, es, ep2, nadgrids) { + const out = {}; + + if (datumCode === undefined || datumCode === 'none') { + out.datum_type = PJD_NODATUM; + } else { + out.datum_type = PJD_WGS84; + } + + if (datum_params) { + out.datum_params = datum_params.map(parseFloat); + if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) { + out.datum_type = PJD_3PARAM; + } + if (out.datum_params.length > 3) { + if ( + out.datum_params[3] !== 0 || + out.datum_params[4] !== 0 || + out.datum_params[5] !== 0 || + out.datum_params[6] !== 0 + ) { + out.datum_type = PJD_7PARAM; + out.datum_params[3] *= SEC_TO_RAD; + out.datum_params[4] *= SEC_TO_RAD; + out.datum_params[5] *= SEC_TO_RAD; + out.datum_params[6] = out.datum_params[6] / 1000000.0 + 1.0; + } + } + } + + if (nadgrids) { + out.datum_type = PJD_GRIDSHIFT; + out.grids = nadgrids; + } + out.a = a; //datum object also uses these values + out.b = b; + out.es = es; + out.ep2 = ep2; + + return out; +} + +/** + * @param source + * @param dest + */ +export function compareDatums(source, dest) { + if (source.datum_type !== dest.datum_type) { + return false; // false, datums are not equal + } else if (source.a !== dest.a || Math.abs(source.es - dest.es) > 0.00000000005) { + // the tolerance for es is to ensure that GRS80 and WGS84 + // are considered identical + return false; + } else if (source.datum_type === PJD_3PARAM) { + return ( + source.datum_params[0] === dest.datum_params[0] && + source.datum_params[1] === dest.datum_params[1] && + source.datum_params[2] === dest.datum_params[2] + ); + } else if (source.datum_type === PJD_7PARAM) { + return ( + source.datum_params[0] === dest.datum_params[0] && + source.datum_params[1] === dest.datum_params[1] && + source.datum_params[2] === dest.datum_params[2] && + source.datum_params[3] === dest.datum_params[3] && + source.datum_params[4] === dest.datum_params[4] && + source.datum_params[5] === dest.datum_params[5] && + source.datum_params[6] === dest.datum_params[6] + ); + } else { + return true; // datums are equal + } +} // cs_compare_datums() + +/* + * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates + * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z), + * according to the current ellipsoid parameters. + * + * Latitude : Geodetic latitude in radians (input) + * Longitude : Geodetic longitude in radians (input) + * Height : Geodetic height, in meters (input) + * X : Calculated Geocentric X coordinate, in meters (output) + * Y : Calculated Geocentric Y coordinate, in meters (output) + * Z : Calculated Geocentric Z coordinate, in meters (output) + * + */ +/** + * @param p + * @param es + * @param a + */ +export function geodeticToGeocentric(p, es, a) { + let Longitude = p.x; + let Latitude = p.y; + const Height = p.z ? p.z : 0; //Z value not always supplied + + let Rn; /* Earth radius at location */ + let Sin_Lat; /* Math.sin(Latitude) */ + let Sin2_Lat; /* Square of Math.sin(Latitude) */ + let Cos_Lat; /* Math.cos(Latitude) */ + + /* + ** Don't blow up if Latitude is just a little out of the value + ** range as it may just be a rounding issue. Also removed longitude + ** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001. + */ + if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) { + Latitude = -HALF_PI; + } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) { + Latitude = HALF_PI; + } else if (Latitude < -HALF_PI) { + /* Latitude out of range */ + //..reportError('geocent:lat out of range:' + Latitude); + return { x: -Infinity, y: -Infinity, z: p.z }; + } else if (Latitude > HALF_PI) { + /* Latitude out of range */ + return { x: Infinity, y: Infinity, z: p.z }; + } + + if (Longitude > Math.PI) { + Longitude -= 2 * Math.PI; + } + Sin_Lat = Math.sin(Latitude); + Cos_Lat = Math.cos(Latitude); + Sin2_Lat = Sin_Lat * Sin_Lat; + Rn = a / Math.sqrt(1.0 - es * Sin2_Lat); + return { + x: (Rn + Height) * Cos_Lat * Math.cos(Longitude), + y: (Rn + Height) * Cos_Lat * Math.sin(Longitude), + z: (Rn * (1 - es) + Height) * Sin_Lat, + }; +} // cs_geodetic_to_geocentric() + +/** + * @param p + * @param es + * @param a + * @param b + */ +export function geocentricToGeodetic(p, es, a, b) { + /* local defintions and variables */ + /* end-criterium of loop, accuracy of sin(Latitude) */ + const genau = 1e-12; + const genau2 = genau * genau; + const maxiter = 30; + + let P; /* distance between semi-minor axis and location */ + let RR; /* distance between center and location */ + let CT; /* sin of geocentric latitude */ + let ST; /* cos of geocentric latitude */ + let RX; + let RK; + let RN; /* Earth radius at location */ + let CPHI0; /* cos of start or old geodetic latitude in iterations */ + let SPHI0; /* sin of start or old geodetic latitude in iterations */ + let CPHI; /* cos of searched geodetic latitude */ + let SPHI; /* sin of searched geodetic latitude */ + let SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ + let iter; /* # of continous iteration, max. 30 is always enough (s.a.) */ + + const X = p.x; + const Y = p.y; + const Z = p.z ? p.z : 0.0; //Z value not always supplied + let Longitude; + let Latitude; + let Height; + + P = Math.sqrt(X * X + Y * Y); + RR = Math.sqrt(X * X + Y * Y + Z * Z); + + /* special cases for latitude and longitude */ + if (P / a < genau) { + /* special case, if P=0. (X=0., Y=0.) */ + Longitude = 0.0; + + /* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis + * of ellipsoid (=center of mass), Latitude becomes PI/2 */ + if (RR / a < genau) { + Latitude = HALF_PI; + Height = -b; + return { + x: p.x, + y: p.y, + z: p.z, + }; + } + } else { + /* ellipsoidal (geodetic) longitude + * interval: -PI < Longitude <= +PI */ + Longitude = Math.atan2(Y, X); + } + + /* -------------------------------------------------------------- + * Following iterative algorithm was developped by + * "Institut for Erdmessung", University of Hannover, July 1988. + * Internet: www.ife.uni-hannover.de + * Iterative computation of CPHI,SPHI and Height. + * Iteration of CPHI and SPHI to 10**-12 radian resp. + * 2*10**-7 arcsec. + * -------------------------------------------------------------- + */ + CT = Z / RR; + ST = P / RR; + RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST); + CPHI0 = ST * (1.0 - es) * RX; + SPHI0 = CT * RX; + iter = 0; + + /* loop to find sin(Latitude) resp. Latitude + * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ + do { + iter++; + RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0); + + /* ellipsoidal (geodetic) height */ + Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0); + + RK = (es * RN) / (RN + Height); + RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST); + CPHI = ST * (1.0 - RK) * RX; + SPHI = CT * RX; + SDPHI = SPHI * CPHI0 - CPHI * SPHI0; + CPHI0 = CPHI; + SPHI0 = SPHI; + } while (SDPHI * SDPHI > genau2 && iter < maxiter); + + /* ellipsoidal (geodetic) latitude */ + Latitude = Math.atan(SPHI / Math.abs(CPHI)); + return { + x: Longitude, + y: Latitude, + z: Height, + }; +} // cs_geocentric_to_geodetic() + +/****************************************************************/ +// pj_geocentic_to_wgs84( p ) +// p = point to transform in geocentric coordinates (x,y,z) + +/** + point object, nothing fancy, just allows values to be + passed back and forth by reference rather than by value. + Other point classes may be used as long as they have + x and y properties, which will get modified in the transform method. + * @param p + * @param datum_type + * @param datum_params + */ +export function geocentricToWgs84(p, datum_type, datum_params) { + if (datum_type === PJD_3PARAM) { + // if( x[io] === HUGE_VAL ) + // continue; + return { + x: p.x + datum_params[0], + y: p.y + datum_params[1], + z: p.z + datum_params[2], + }; + } else if (datum_type === PJD_7PARAM) { + const Dx_BF = datum_params[0]; + const Dy_BF = datum_params[1]; + const Dz_BF = datum_params[2]; + const Rx_BF = datum_params[3]; + const Ry_BF = datum_params[4]; + const Rz_BF = datum_params[5]; + const M_BF = datum_params[6]; + // if( x[io] === HUGE_VAL ) + // continue; + return { + x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF, + y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF, + z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF, + }; + } +} // cs_geocentric_to_wgs84 + +/****************************************************************/ +// pj_geocentic_from_wgs84() +// coordinate system definition, +// point to transform in geocentric coordinates (x,y,z) +/** + * @param p + * @param datum_type + * @param datum_params + */ +export function geocentricFromWgs84(p, datum_type, datum_params) { + if (datum_type === PJD_3PARAM) { + //if( x[io] === HUGE_VAL ) + // continue; + return { + x: p.x - datum_params[0], + y: p.y - datum_params[1], + z: p.z - datum_params[2], + }; + } else if (datum_type === PJD_7PARAM) { + const Dx_BF = datum_params[0]; + const Dy_BF = datum_params[1]; + const Dz_BF = datum_params[2]; + const Rx_BF = datum_params[3]; + const Ry_BF = datum_params[4]; + const Rz_BF = datum_params[5]; + const M_BF = datum_params[6]; + const x_tmp = (p.x - Dx_BF) / M_BF; + const y_tmp = (p.y - Dy_BF) / M_BF; + const z_tmp = (p.z - Dz_BF) / M_BF; + //if( x[io] === HUGE_VAL ) + // continue; + + return { + x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp, + y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp, + z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp, + }; + } //cs_geocentric_from_wgs84() +} + +/** + * @param type + */ +function checkParams(type) { + return type === PJD_3PARAM || type === PJD_7PARAM; +} + +/** + * @param source + * @param dest + * @param point + */ +export default function (source, dest, point) { + // Short cut if the datums are identical. + if (compareDatums(source, dest)) { + return point; // in this case, zero is sucess, + // whereas cs_compare_datums returns 1 to indicate TRUE + // confusing, should fix this + } + + // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest + if (source.datum_type === PJD_NODATUM || dest.datum_type === PJD_NODATUM) { + return point; + } + + // If this datum requires grid shifts, then apply it to geodetic coordinates. + let source_a = source.a; + let source_es = source.es; + if (source.datum_type === PJD_GRIDSHIFT) { + const gridShiftCode = applyGridShift(source, false, point); + if (gridShiftCode !== 0) { + return undefined; + } + source_a = SRS_WGS84_SEMIMAJOR; + source_es = SRS_WGS84_ESQUARED; + } + + let dest_a = dest.a; + let dest_b = dest.b; + let dest_es = dest.es; + if (dest.datum_type === PJD_GRIDSHIFT) { + dest_a = SRS_WGS84_SEMIMAJOR; + dest_b = SRS_WGS84_SEMIMINOR; + dest_es = SRS_WGS84_ESQUARED; + } + + // Do we need to go through geocentric coordinates? + if ( + source_es === dest_es && + source_a === dest_a && + !checkParams(source.datum_type) && + !checkParams(dest.datum_type) + ) { + return point; + } + + // Convert to geocentric coordinates. + point = geodeticToGeocentric(point, source_es, source_a); + // Convert between datums + if (checkParams(source.datum_type)) { + point = geocentricToWgs84(point, source.datum_type, source.datum_params); + } + if (checkParams(dest.datum_type)) { + point = geocentricFromWgs84(point, dest.datum_type, dest.datum_params); + } + point = geocentricToGeodetic(point, dest_es, dest_a, dest_b); + + if (dest.datum_type === PJD_GRIDSHIFT) { + const destGridShiftResult = applyGridShift(dest, true, point); + if (destGridShiftResult !== 0) { + return undefined; + } + } + + return point; +} + +/** + * @param source + * @param inverse + * @param point + */ +export function applyGridShift(source, inverse, point) { + if (source.grids === null || source.grids.length === 0) { + throw new Error('Grid shift grids not found'); + } + const input = { x: -point.x, y: point.y }; + let output = { x: Number.NaN, y: Number.NaN }; + let onlyMandatoryGrids = false; + const attemptedGrids = []; + outer: for (let i = 0; i < source.grids.length; i++) { + const grid = source.grids[i]; + attemptedGrids.push(grid.name); + if (grid.isNull) { + output = input; + break; + } + onlyMandatoryGrids = grid.mandatory; + if (grid.grid === null) { + if (grid.mandatory) { + throw new Error(`Unable to find mandatory grid '${grid.name}'`); + } + continue; + } + const subgrids = grid.grid.subgrids; + for (let j = 0, jj = subgrids.length; j < jj; j++) { + const subgrid = subgrids[j]; + // skip tables that don't match our point at all + const epsilon = (Math.abs(subgrid.del[1]) + Math.abs(subgrid.del[0])) / 10000.0; + const minX = subgrid.ll[0] - epsilon; + const minY = subgrid.ll[1] - epsilon; + const maxX = subgrid.ll[0] + (subgrid.lim[0] - 1) * subgrid.del[0] + epsilon; + const maxY = subgrid.ll[1] + (subgrid.lim[1] - 1) * subgrid.del[1] + epsilon; + if (minY > input.y || minX > input.x || maxY < input.y || maxX < input.x) { + continue; + } + output = applySubgridShift(input, inverse, subgrid); + if (!isNaN(output.x)) { + break outer; + } + } + } + if (isNaN(output.x)) { + throw new Error( + `Failed to find a grid shift table for location '${-input.x * R2D} ${input.y * R2D}' tried: '${attemptedGrids}'`, + ); + } + point.x = -output.x; + point.y = output.y; + return 0; +} + +/** + * @param pin + * @param inverse + * @param ct + */ +function applySubgridShift(pin, inverse, ct) { + const val = { x: Number.NaN, y: Number.NaN }; + if (isNaN(pin.x)) { + return val; + } + const tb = { x: pin.x, y: pin.y }; + tb.x -= ct.ll[0]; + tb.y -= ct.ll[1]; + tb.x = adjustLon(tb.x - Math.PI) + Math.PI; + const t = nadInterpolate(tb, ct); + if (inverse) { + if (isNaN(t.x)) { + return val; + } + t.x = tb.x - t.x; + t.y = tb.y - t.y; + let i = 9, + tol = 1e-12; + let dif, del; + do { + del = nadInterpolate(t, ct); + if (isNaN(del.x)) { + throw new Error( + 'Inverse grid shift iteration failed, presumably at grid edge. Using first approximation.', + ); + } + dif = { x: tb.x - (del.x + t.x), y: tb.y - (del.y + t.y) }; + t.x += dif.x; + t.y += dif.y; + } while (i-- && Math.abs(dif.x) > tol && Math.abs(dif.y) > tol); + if (i < 0) { + throw new Error('Inverse grid shift iterator failed to converge.'); + } + val.x = adjustLon(t.x + ct.ll[0]); + val.y = t.y + ct.ll[1]; + } else { + if (!isNaN(t.x)) { + val.x = pin.x + t.x; + val.y = pin.y + t.y; + } + } + return val; +} + +/** + * @param pin + * @param ct + */ +function nadInterpolate(pin: VectorPoint, ct): VectorPoint { + const t = { x: pin.x / ct.del[0], y: pin.y / ct.del[1] }; + const indx = { x: Math.floor(t.x), y: Math.floor(t.y) }; + const frct = { x: t.x - 1.0 * indx.x, y: t.y - 1.0 * indx.y }; + const val = { x: NaN, y: NaN }; + let inx; + if (indx.x < 0 || indx.x >= ct.lim[0]) return val; + if (indx.y < 0 || indx.y >= ct.lim[1]) return val; + inx = indx.y * ct.lim[0] + indx.x; + const f00 = { x: ct.cvs[inx][0], y: ct.cvs[inx][1] }; + inx++; + const f10 = { x: ct.cvs[inx][0], y: ct.cvs[inx][1] }; + inx += ct.lim[0]; + const f11 = { x: ct.cvs[inx][0], y: ct.cvs[inx][1] }; + inx--; + const f01 = { x: ct.cvs[inx][0], y: ct.cvs[inx][1] }; + const m11 = frct.x * frct.y, + m10 = frct.x * (1.0 - frct.y), + m00 = (1.0 - frct.x) * (1.0 - frct.y), + m01 = (1.0 - frct.x) * frct.y; + val.x = m00 * f00.x + m10 * f10.x + m01 * f01.x + m11 * f11.x; + val.y = m00 * f00.y + m10 * f10.y + m01 * f01.y + m11 * f11.y; + + return val; +} diff --git a/src/proj4/datumUtils.js b/src/proj4/datumUtils.js deleted file mode 100644 index 5eb07c66..00000000 --- a/src/proj4/datumUtils.js +++ /dev/null @@ -1,245 +0,0 @@ -'use strict'; -import {PJD_3PARAM, PJD_7PARAM, HALF_PI} from './constants/values'; -export function compareDatums(source, dest) { - if (source.datum_type !== dest.datum_type) { - return false; // false, datums are not equal - } else if (source.a !== dest.a || Math.abs(source.es - dest.es) > 0.000000000050) { - // the tolerance for es is to ensure that GRS80 and WGS84 - // are considered identical - return false; - } else if (source.datum_type === PJD_3PARAM) { - return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2]); - } else if (source.datum_type === PJD_7PARAM) { - return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2] && source.datum_params[3] === dest.datum_params[3] && source.datum_params[4] === dest.datum_params[4] && source.datum_params[5] === dest.datum_params[5] && source.datum_params[6] === dest.datum_params[6]); - } else { - return true; // datums are equal - } -} // cs_compare_datums() - -/* - * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates - * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z), - * according to the current ellipsoid parameters. - * - * Latitude : Geodetic latitude in radians (input) - * Longitude : Geodetic longitude in radians (input) - * Height : Geodetic height, in meters (input) - * X : Calculated Geocentric X coordinate, in meters (output) - * Y : Calculated Geocentric Y coordinate, in meters (output) - * Z : Calculated Geocentric Z coordinate, in meters (output) - * - */ -export function geodeticToGeocentric(p, es, a) { - var Longitude = p.x; - var Latitude = p.y; - var Height = p.z ? p.z : 0; //Z value not always supplied - - var Rn; /* Earth radius at location */ - var Sin_Lat; /* Math.sin(Latitude) */ - var Sin2_Lat; /* Square of Math.sin(Latitude) */ - var Cos_Lat; /* Math.cos(Latitude) */ - - /* - ** Don't blow up if Latitude is just a little out of the value - ** range as it may just be a rounding issue. Also removed longitude - ** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001. - */ - if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) { - Latitude = -HALF_PI; - } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) { - Latitude = HALF_PI; - } else if (Latitude < -HALF_PI) { - /* Latitude out of range */ - //..reportError('geocent:lat out of range:' + Latitude); - return { x: -Infinity, y: -Infinity, z: p.z }; - } else if (Latitude > HALF_PI) { - /* Latitude out of range */ - return { x: Infinity, y: Infinity, z: p.z }; - } - - if (Longitude > Math.PI) { - Longitude -= (2 * Math.PI); - } - Sin_Lat = Math.sin(Latitude); - Cos_Lat = Math.cos(Latitude); - Sin2_Lat = Sin_Lat * Sin_Lat; - Rn = a / (Math.sqrt(1.0e0 - es * Sin2_Lat)); - return { - x: (Rn + Height) * Cos_Lat * Math.cos(Longitude), - y: (Rn + Height) * Cos_Lat * Math.sin(Longitude), - z: ((Rn * (1 - es)) + Height) * Sin_Lat - }; -} // cs_geodetic_to_geocentric() - -export function geocentricToGeodetic(p, es, a, b) { - /* local defintions and variables */ - /* end-criterium of loop, accuracy of sin(Latitude) */ - var genau = 1e-12; - var genau2 = (genau * genau); - var maxiter = 30; - - var P; /* distance between semi-minor axis and location */ - var RR; /* distance between center and location */ - var CT; /* sin of geocentric latitude */ - var ST; /* cos of geocentric latitude */ - var RX; - var RK; - var RN; /* Earth radius at location */ - var CPHI0; /* cos of start or old geodetic latitude in iterations */ - var SPHI0; /* sin of start or old geodetic latitude in iterations */ - var CPHI; /* cos of searched geodetic latitude */ - var SPHI; /* sin of searched geodetic latitude */ - var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ - var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */ - - var X = p.x; - var Y = p.y; - var Z = p.z ? p.z : 0.0; //Z value not always supplied - var Longitude; - var Latitude; - var Height; - - P = Math.sqrt(X * X + Y * Y); - RR = Math.sqrt(X * X + Y * Y + Z * Z); - - /* special cases for latitude and longitude */ - if (P / a < genau) { - - /* special case, if P=0. (X=0., Y=0.) */ - Longitude = 0.0; - - /* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis - * of ellipsoid (=center of mass), Latitude becomes PI/2 */ - if (RR / a < genau) { - Latitude = HALF_PI; - Height = -b; - return { - x: p.x, - y: p.y, - z: p.z - }; - } - } else { - /* ellipsoidal (geodetic) longitude - * interval: -PI < Longitude <= +PI */ - Longitude = Math.atan2(Y, X); - } - - /* -------------------------------------------------------------- - * Following iterative algorithm was developped by - * "Institut for Erdmessung", University of Hannover, July 1988. - * Internet: www.ife.uni-hannover.de - * Iterative computation of CPHI,SPHI and Height. - * Iteration of CPHI and SPHI to 10**-12 radian resp. - * 2*10**-7 arcsec. - * -------------------------------------------------------------- - */ - CT = Z / RR; - ST = P / RR; - RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST); - CPHI0 = ST * (1.0 - es) * RX; - SPHI0 = CT * RX; - iter = 0; - - /* loop to find sin(Latitude) resp. Latitude - * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ - do { - iter++; - RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0); - - /* ellipsoidal (geodetic) height */ - Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0); - - RK = es * RN / (RN + Height); - RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST); - CPHI = ST * (1.0 - RK) * RX; - SPHI = CT * RX; - SDPHI = SPHI * CPHI0 - CPHI * SPHI0; - CPHI0 = CPHI; - SPHI0 = SPHI; - } - while (SDPHI * SDPHI > genau2 && iter < maxiter); - - /* ellipsoidal (geodetic) latitude */ - Latitude = Math.atan(SPHI / Math.abs(CPHI)); - return { - x: Longitude, - y: Latitude, - z: Height - }; -} // cs_geocentric_to_geodetic() - -/****************************************************************/ -// pj_geocentic_to_wgs84( p ) -// p = point to transform in geocentric coordinates (x,y,z) - - -/** point object, nothing fancy, just allows values to be - passed back and forth by reference rather than by value. - Other point classes may be used as long as they have - x and y properties, which will get modified in the transform method. -*/ -export function geocentricToWgs84(p, datum_type, datum_params) { - - if (datum_type === PJD_3PARAM) { - // if( x[io] === HUGE_VAL ) - // continue; - return { - x: p.x + datum_params[0], - y: p.y + datum_params[1], - z: p.z + datum_params[2], - }; - } else if (datum_type === PJD_7PARAM) { - var Dx_BF = datum_params[0]; - var Dy_BF = datum_params[1]; - var Dz_BF = datum_params[2]; - var Rx_BF = datum_params[3]; - var Ry_BF = datum_params[4]; - var Rz_BF = datum_params[5]; - var M_BF = datum_params[6]; - // if( x[io] === HUGE_VAL ) - // continue; - return { - x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF, - y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF, - z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF - }; - } -} // cs_geocentric_to_wgs84 - -/****************************************************************/ -// pj_geocentic_from_wgs84() -// coordinate system definition, -// point to transform in geocentric coordinates (x,y,z) -export function geocentricFromWgs84(p, datum_type, datum_params) { - - if (datum_type === PJD_3PARAM) { - //if( x[io] === HUGE_VAL ) - // continue; - return { - x: p.x - datum_params[0], - y: p.y - datum_params[1], - z: p.z - datum_params[2], - }; - - } else if (datum_type === PJD_7PARAM) { - var Dx_BF = datum_params[0]; - var Dy_BF = datum_params[1]; - var Dz_BF = datum_params[2]; - var Rx_BF = datum_params[3]; - var Ry_BF = datum_params[4]; - var Rz_BF = datum_params[5]; - var M_BF = datum_params[6]; - var x_tmp = (p.x - Dx_BF) / M_BF; - var y_tmp = (p.y - Dy_BF) / M_BF; - var z_tmp = (p.z - Dz_BF) / M_BF; - //if( x[io] === HUGE_VAL ) - // continue; - - return { - x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp, - y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp, - z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp - }; - } //cs_geocentric_from_wgs84() -} diff --git a/src/proj4/datum_transform.js b/src/proj4/datum_transform.js deleted file mode 100644 index 8d2856b3..00000000 --- a/src/proj4/datum_transform.js +++ /dev/null @@ -1,196 +0,0 @@ -import { - PJD_3PARAM, - PJD_7PARAM, - PJD_GRIDSHIFT, - PJD_NODATUM, - R2D, - SRS_WGS84_ESQUARED, - SRS_WGS84_SEMIMAJOR, SRS_WGS84_SEMIMINOR -} from './constants/values'; - -import {geodeticToGeocentric, geocentricToGeodetic, geocentricToWgs84, geocentricFromWgs84, compareDatums} from '../lib/datumUtils'; -import adjust_lon from "./common/adjust_lon"; -function checkParams(type) { - return (type === PJD_3PARAM || type === PJD_7PARAM); -} - -export default function(source, dest, point) { - // Short cut if the datums are identical. - if (compareDatums(source, dest)) { - return point; // in this case, zero is sucess, - // whereas cs_compare_datums returns 1 to indicate TRUE - // confusing, should fix this - } - - // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest - if (source.datum_type === PJD_NODATUM || dest.datum_type === PJD_NODATUM) { - return point; - } - - // If this datum requires grid shifts, then apply it to geodetic coordinates. - var source_a = source.a; - var source_es = source.es; - if (source.datum_type === PJD_GRIDSHIFT) { - var gridShiftCode = applyGridShift(source, false, point); - if (gridShiftCode !== 0) { - return undefined; - } - source_a = SRS_WGS84_SEMIMAJOR; - source_es = SRS_WGS84_ESQUARED; - } - - var dest_a = dest.a; - var dest_b = dest.b; - var dest_es = dest.es; - if (dest.datum_type === PJD_GRIDSHIFT) { - dest_a = SRS_WGS84_SEMIMAJOR; - dest_b = SRS_WGS84_SEMIMINOR; - dest_es = SRS_WGS84_ESQUARED; - } - - // Do we need to go through geocentric coordinates? - if (source_es === dest_es && source_a === dest_a && !checkParams(source.datum_type) && !checkParams(dest.datum_type)) { - return point; - } - - // Convert to geocentric coordinates. - point = geodeticToGeocentric(point, source_es, source_a); - // Convert between datums - if (checkParams(source.datum_type)) { - point = geocentricToWgs84(point, source.datum_type, source.datum_params); - } - if (checkParams(dest.datum_type)) { - point = geocentricFromWgs84(point, dest.datum_type, dest.datum_params); - } - point = geocentricToGeodetic(point, dest_es, dest_a, dest_b); - - if (dest.datum_type === PJD_GRIDSHIFT) { - var destGridShiftResult = applyGridShift(dest, true, point); - if (destGridShiftResult !== 0) { - return undefined; - } - } - - return point; -} - -export function applyGridShift(source, inverse, point) { - if (source.grids === null || source.grids.length === 0) { - console.log('Grid shift grids not found'); - return -1; - } - var input = {x: -point.x, y: point.y}; - var output = {x: Number.NaN, y: Number.NaN}; - var onlyMandatoryGrids = false; - var attemptedGrids = []; - outer: - for (var i = 0; i < source.grids.length; i++) { - var grid = source.grids[i]; - attemptedGrids.push(grid.name); - if (grid.isNull) { - output = input; - break; - } - onlyMandatoryGrids = grid.mandatory; - if (grid.grid === null) { - if (grid.mandatory) { - console.log("Unable to find mandatory grid '" + grid.name + "'"); - return -1; - } - continue; - } - var subgrids = grid.grid.subgrids; - for (var j = 0, jj = subgrids.length; j < jj; j++) { - var subgrid = subgrids[j]; - // skip tables that don't match our point at all - var epsilon = (Math.abs(subgrid.del[1]) + Math.abs(subgrid.del[0])) / 10000.0; - var minX = subgrid.ll[0] - epsilon; - var minY = subgrid.ll[1] - epsilon; - var maxX = subgrid.ll[0] + (subgrid.lim[0] - 1) * subgrid.del[0] + epsilon; - var maxY = subgrid.ll[1] + (subgrid.lim[1] - 1) * subgrid.del[1] + epsilon; - if (minY > input.y || minX > input.x || maxY < input.y || maxX < input.x ) { - continue; - } - output = applySubgridShift(input, inverse, subgrid); - if (!isNaN(output.x)) { - break outer; - } - } - } - if (isNaN(output.x)) { - console.log("Failed to find a grid shift table for location '"+ - -input.x * R2D + " " + input.y * R2D + " tried: '" + attemptedGrids + "'"); - return -1; - } - point.x = -output.x; - point.y = output.y; - return 0; -} - -function applySubgridShift(pin, inverse, ct) { - var val = {x: Number.NaN, y: Number.NaN}; - if (isNaN(pin.x)) { return val; } - var tb = {x: pin.x, y: pin.y}; - tb.x -= ct.ll[0]; - tb.y -= ct.ll[1]; - tb.x = adjust_lon(tb.x - Math.PI) + Math.PI; - var t = nadInterpolate(tb, ct); - if (inverse) { - if (isNaN(t.x)) { - return val; - } - t.x = tb.x - t.x; - t.y = tb.y - t.y; - var i = 9, tol = 1e-12; - var dif, del; - do { - del = nadInterpolate(t, ct); - if (isNaN(del.x)) { - console.log("Inverse grid shift iteration failed, presumably at grid edge. Using first approximation."); - break; - } - dif = {x: tb.x - (del.x + t.x), y: tb.y - (del.y + t.y)}; - t.x += dif.x; - t.y += dif.y; - } while (i-- && Math.abs(dif.x) > tol && Math.abs(dif.y) > tol); - if (i < 0) { - console.log("Inverse grid shift iterator failed to converge."); - return val; - } - val.x = adjust_lon(t.x + ct.ll[0]); - val.y = t.y + ct.ll[1]; - } else { - if (!isNaN(t.x)) { - val.x = pin.x + t.x; - val.y = pin.y + t.y; - } - } - return val; -} - -function nadInterpolate(pin, ct) { - var t = {x: pin.x / ct.del[0], y: pin.y / ct.del[1]}; - var indx = {x: Math.floor(t.x), y: Math.floor(t.y)}; - var frct = {x: t.x - 1.0 * indx.x, y: t.y - 1.0 * indx.y}; - var val= {x: Number.NaN, y: Number.NaN}; - var inx; - if (indx.x < 0 || indx.x >= ct.lim[0]) { - return val; - } - if (indx.y < 0 || indx.y >= ct.lim[1]) { - return val; - } - inx = (indx.y * ct.lim[0]) + indx.x; - var f00 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; - inx++; - var f10= {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; - inx += ct.lim[0]; - var f11 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; - inx--; - var f01 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; - var m11 = frct.x * frct.y, m10 = frct.x * (1.0 - frct.y), - m00 = (1.0 - frct.x) * (1.0 - frct.y), m01 = (1.0 - frct.x) * frct.y; - val.x = (m00 * f00.x + m10 * f10.x + m01 * f01.x + m11 * f11.x); - val.y = (m00 * f00.y + m10 * f10.y + m01 * f01.y + m11 * f11.y); - return val; -} diff --git a/src/proj4/deriveConstants.js b/src/proj4/deriveConstants.js deleted file mode 100644 index fd8b382a..00000000 --- a/src/proj4/deriveConstants.js +++ /dev/null @@ -1,48 +0,0 @@ -import {SIXTH, RA4, RA6, EPSLN} from './constants/values'; -import {default as Ellipsoid, WGS84} from './constants/Ellipsoid'; -import match from './match'; - -export function eccentricity(a, b, rf, R_A) { - var a2 = a * a; // used in geocentric - var b2 = b * b; // used in geocentric - var es = (a2 - b2) / a2; // e ^ 2 - var e = 0; - if (R_A) { - a *= 1 - es * (SIXTH + es * (RA4 + es * RA6)); - a2 = a * a; - es = 0; - } else { - e = Math.sqrt(es); // eccentricity - } - var ep2 = (a2 - b2) / b2; // used in geocentric - return { - es: es, - e: e, - ep2: ep2 - }; -} -export function sphere(a, b, rf, ellps, sphere) { - if (!a) { // do we have an ellipsoid? - var ellipse = match(Ellipsoid, ellps); - if (!ellipse) { - ellipse = WGS84; - } - a = ellipse.a; - b = ellipse.b; - rf = ellipse.rf; - } - - if (rf && !b) { - b = (1.0 - 1.0 / rf) * a; - } - if (rf === 0 || Math.abs(a - b) < EPSLN) { - sphere = true; - b = a; - } - return { - a: a, - b: b, - rf: rf, - sphere: sphere - }; -} diff --git a/src/proj4/extend.js b/src/proj4/extend.js deleted file mode 100644 index bcd759f9..00000000 --- a/src/proj4/extend.js +++ /dev/null @@ -1,14 +0,0 @@ -export default function(destination, source) { - destination = destination || {}; - var value, property; - if (!source) { - return destination; - } - for (property in source) { - value = source[property]; - if (value !== undefined) { - destination[property] = value; - } - } - return destination; -} diff --git a/src/proj4/global.js b/src/proj4/global.js deleted file mode 100644 index 476a131a..00000000 --- a/src/proj4/global.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function(defs) { - defs('EPSG:4326', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees"); - defs('EPSG:4269', "+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees"); - defs('EPSG:3857', "+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"); - - defs.WGS84 = defs['EPSG:4326']; - defs['EPSG:3785'] = defs['EPSG:3857']; // maintain backward compat, official code is 3857 - defs.GOOGLE = defs['EPSG:3857']; - defs['EPSG:900913'] = defs['EPSG:3857']; - defs['EPSG:102113'] = defs['EPSG:3857']; -} diff --git a/src/proj4/includedProjections.js b/src/proj4/includedProjections.js deleted file mode 100644 index 3c2fdb6b..00000000 --- a/src/proj4/includedProjections.js +++ /dev/null @@ -1,69 +0,0 @@ -import tmerc from "./projections/tmerc"; -import utm from "./projections/utm"; -import sterea from "./projections/sterea"; -import stere from "./projections/stere"; -import somerc from "./projections/somerc"; -import omerc from "./projections/omerc"; -import lcc from "./projections/lcc"; -import krovak from "./projections/krovak"; -import cass from "./projections/cass"; -import laea from "./projections/laea"; -import aea from "./projections/aea"; -import gnom from "./projections/gnom"; -import cea from "./projections/cea"; -import eqc from "./projections/eqc"; -import poly from "./projections/poly"; -import nzmg from "./projections/nzmg"; -import mill from "./projections/mill"; -import sinu from "./projections/sinu"; -import moll from "./projections/moll"; -import eqdc from "./projections/eqdc"; -import vandg from "./projections/vandg"; -import aegd from "./projections/aeqd"; -import etmerc from './projections/etmerc'; -import qsc from './projections/qsc'; -import robin from './projections/robin'; -import geocent from './projections/geocent'; -import tpers from './projections/tpers'; -import geos from './projections/geos'; -import eqearth from "./projections/eqearth"; -import bonne from "./projections/bonne"; - -var projs = [ - tmerc, - utm, - sterea, - stere, - somerc, - omerc, - lcc, - krovak, - cass, - laea, - aea, - gnom, - cea, - eqc, - poly, - nzmg, - mill, - sinu, - moll, - eqdc, - vandg, - aegd, - etmerc, - qsc, - robin, - geocent, - tpers, - geos, - eqearth, - bonne -]; - -export default function (proj4) { - projs.forEach(function (proj) { - proj4.Proj.projections.add(proj); - }); -} \ No newline at end of file diff --git a/src/proj4/index.ts b/src/proj4/index.ts new file mode 100644 index 00000000..62e4fbad --- /dev/null +++ b/src/proj4/index.ts @@ -0,0 +1,4 @@ +export * from './constants'; +export * from './projections'; +export * from './common'; +export * from './transformer'; diff --git a/src/proj4/match.js b/src/proj4/match.js deleted file mode 100644 index 595e1667..00000000 --- a/src/proj4/match.js +++ /dev/null @@ -1,17 +0,0 @@ -var ignoredChar = /[\s_\-\/\(\)]/g; -export default function match(obj, key) { - if (obj[key]) { - return obj[key]; - } - var keys = Object.keys(obj); - var lkey = key.toLowerCase().replace(ignoredChar, ''); - var i = -1; - var testkey, processedKey; - while (++i < keys.length) { - testkey = keys[i]; - processedKey = testkey.toLowerCase().replace(ignoredChar, ''); - if (processedKey === lkey) { - return obj[testkey]; - } - } -} diff --git a/src/proj4/nadgrid.js b/src/proj4/nadgrid.js deleted file mode 100644 index 1924ef06..00000000 --- a/src/proj4/nadgrid.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Resources for details of NTv2 file formats: - * - https://web.archive.org/web/20140127204822if_/http://www.mgs.gov.on.ca:80/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf - * - http://mimaka.com/help/gs/html/004_NTV2%20Data%20Format.htm - */ - -var loadedNadgrids = {}; - -/** - * Load a binary NTv2 file (.gsb) to a key that can be used in a proj string like +nadgrids=. Pass the NTv2 file - * as an ArrayBuffer. - */ -export default function nadgrid(key, data) { - var view = new DataView(data); - var isLittleEndian = detectLittleEndian(view); - var header = readHeader(view, isLittleEndian); - var subgrids = readSubgrids(view, header, isLittleEndian); - var nadgrid = {header: header, subgrids: subgrids}; - loadedNadgrids[key] = nadgrid; - return nadgrid; -} - -/** - * Given a proj4 value for nadgrids, return an array of loaded grids - */ -export function getNadgrids(nadgrids) { - // Format details: http://proj.maptools.org/gen_parms.html - if (nadgrids === undefined) { return null; } - var grids = nadgrids.split(','); - return grids.map(parseNadgridString); -} - -function parseNadgridString(value) { - if (value.length === 0) { - return null; - } - var optional = value[0] === '@'; - if (optional) { - value = value.slice(1); - } - if (value === 'null') { - return {name: 'null', mandatory: !optional, grid: null, isNull: true}; - } - return { - name: value, - mandatory: !optional, - grid: loadedNadgrids[value] || null, - isNull: false - }; -} - -function secondsToRadians(seconds) { - return (seconds / 3600) * Math.PI / 180; -} - -function detectLittleEndian(view) { - var nFields = view.getInt32(8, false); - if (nFields === 11) { - return false; - } - nFields = view.getInt32(8, true); - if (nFields !== 11) { - console.warn('Failed to detect nadgrid endian-ness, defaulting to little-endian'); - } - return true; -} - -function readHeader(view, isLittleEndian) { - return { - nFields: view.getInt32(8, isLittleEndian), - nSubgridFields: view.getInt32(24, isLittleEndian), - nSubgrids: view.getInt32(40, isLittleEndian), - shiftType: decodeString(view, 56, 56 + 8).trim(), - fromSemiMajorAxis: view.getFloat64(120, isLittleEndian), - fromSemiMinorAxis: view.getFloat64(136, isLittleEndian), - toSemiMajorAxis: view.getFloat64(152, isLittleEndian), - toSemiMinorAxis: view.getFloat64(168, isLittleEndian), - }; -} - -function decodeString(view, start, end) { - return String.fromCharCode.apply(null, new Uint8Array(view.buffer.slice(start, end))); -} - -function readSubgrids(view, header, isLittleEndian) { - var gridOffset = 176; - var grids = []; - for (var i = 0; i < header.nSubgrids; i++) { - var subHeader = readGridHeader(view, gridOffset, isLittleEndian); - var nodes = readGridNodes(view, gridOffset, subHeader, isLittleEndian); - var lngColumnCount = Math.round( - 1 + (subHeader.upperLongitude - subHeader.lowerLongitude) / subHeader.longitudeInterval); - var latColumnCount = Math.round( - 1 + (subHeader.upperLatitude - subHeader.lowerLatitude) / subHeader.latitudeInterval); - // Proj4 operates on radians whereas the coordinates are in seconds in the grid - grids.push({ - ll: [secondsToRadians(subHeader.lowerLongitude), secondsToRadians(subHeader.lowerLatitude)], - del: [secondsToRadians(subHeader.longitudeInterval), secondsToRadians(subHeader.latitudeInterval)], - lim: [lngColumnCount, latColumnCount], - count: subHeader.gridNodeCount, - cvs: mapNodes(nodes) - }); - gridOffset += 176 + subHeader.gridNodeCount * 16; - } - return grids; -} - -function mapNodes(nodes) { - return nodes.map(function (r) {return [secondsToRadians(r.longitudeShift), secondsToRadians(r.latitudeShift)];}); -} - -function readGridHeader(view, offset, isLittleEndian) { - return { - name: decodeString(view, offset + 8, offset + 16).trim(), - parent: decodeString(view, offset + 24, offset + 24 + 8).trim(), - lowerLatitude: view.getFloat64(offset + 72, isLittleEndian), - upperLatitude: view.getFloat64(offset + 88, isLittleEndian), - lowerLongitude: view.getFloat64(offset + 104, isLittleEndian), - upperLongitude: view.getFloat64(offset + 120, isLittleEndian), - latitudeInterval: view.getFloat64(offset + 136, isLittleEndian), - longitudeInterval: view.getFloat64(offset + 152, isLittleEndian), - gridNodeCount: view.getInt32(offset + 168, isLittleEndian) - }; -} - -function readGridNodes(view, offset, gridHeader, isLittleEndian) { - var nodesOffset = offset + 176; - var gridRecordLength = 16; - var gridShiftRecords = []; - for (var i = 0; i < gridHeader.gridNodeCount; i++) { - var record = { - latitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength, isLittleEndian), - longitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength + 4, isLittleEndian), - latitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 8, isLittleEndian), - longitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 12, isLittleEndian), - }; - gridShiftRecords.push(record); - } - return gridShiftRecords; -} diff --git a/src/proj4/parseCode.js b/src/proj4/parseCode.js index 6d2f95fe..4737330a 100644 --- a/src/proj4/parseCode.js +++ b/src/proj4/parseCode.js @@ -1,7 +1,7 @@ import defs from './defs'; import wkt from 'wkt-parser'; import projStr from './projString'; -import match from './match'; +import match from './util/match'; function testObj(code){ return typeof code === 'string'; } @@ -10,7 +10,7 @@ function testDef(code){ } var codeWords = ['PROJECTEDCRS', 'PROJCRS', 'GEOGCS','GEOCCS','PROJCS','LOCAL_CS', 'GEODCRS', 'GEODETICCRS', 'GEODETICDATUM', 'ENGCRS', 'ENGINEERINGCRS']; function testWKT(code){ - return codeWords.some(function (word) { + return codeWords.some((word) => { return code.indexOf(word) > -1; }); } diff --git a/src/proj4/projString.js b/src/proj4/projString.js index 7f52a73b..98b91853 100644 --- a/src/proj4/projString.js +++ b/src/proj4/projString.js @@ -1,7 +1,7 @@ import {D2R} from './constants/values'; import PrimeMeridian from './constants/PrimeMeridian'; import units from './constants/units'; -import match from './match'; +import match from './util/match'; export default function(defData) { var self = {}; diff --git a/src/proj4/projections/aea.ts b/src/proj4/projections/aea.ts index e5009990..99837a79 100644 --- a/src/proj4/projections/aea.ts +++ b/src/proj4/projections/aea.ts @@ -1,74 +1,107 @@ import { EPSLN } from '../constants'; -import { ProjectionTransform } from '.'; +import { WGS84Projection } from '.'; import { adjustLon, asinz, msfnz, qsfnz } from '../common'; -import type { VectorPoint } from 's2json-spec'; +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const { abs, pow, sin, cos, sqrt, atan2, asin, log } = Math; /** + * # Albers Conic Equal Area Projection + * + * **Classification**: Conic + * + * **Available forms**: Forward and inverse, spherical and ellipsoidal + * + * **Defined area**: Global + * + * **Alias**: `aea` + * + * **Domain**: 2D + * + * **Input type**: Geodetic coordinates * + * **Output type**: Projected coordinates + * + * ## Projection String + * ``` + * +proj=aea +lat_1=29.5 +lat_2=42.5 + * ``` + * + * ## Required Parameters + * - `lat1` + * - `lat2` + * + * ## Optional Parameters + * - `lon0` + * - `ellps` + * - `R` + * - `x0` + * - `y0` + * + * ![Albers Conic Equal Area Projection](https://github.com/OSGeo/PROJ/blob/38dd7c2446f3500a43f0257f5a4833d6aa5aab0b/docs/source/operations/projections/images/aea.png?raw=true) */ -export class AlbersConicEqualArea extends ProjectionTransform { +export class AlbersConicEqualArea extends WGS84Projection implements ProjectionTransformDefinition { name = 'Albers_Conic_Equal_Area'; names = [this.name, 'Albers', 'aea']; // AlbersConicEqualArea specific variables - ns0: number; - temp: number; - e3: number; - sin_po: number; - cos_po: number; - t1: number; - con: number; - ms1: number; - qs1: number; - t2: number; - ms2: number; - qs2: number; - t3: number; - qs0: number; - c: number; - rh: number; - - /** - * @param srsCode - */ - constructor(srsCode?: string) { - super(srsCode); - if (Math.abs(this.lat1 + this.lat2) < EPSLN) { + ns0 = 0; + temp = 0; + e3 = 0; + sin_po = 0; + cos_po = 0; + t1 = 0; + con = 0; + ms1 = 0; + qs1 = 0; + t2 = 0; + ms2 = 0; + qs2 = 0; + t3 = 0; + qs0 = 0; + c = 0; + rh = 0; + + /** Preps an Albers Conic Equal Area projection */ + constructor() { + super(); + if (abs(this.lat1 + this.lat2) < EPSLN) { throw new Error('cass:pj_init_aea: lat1 == lat2'); } this.temp = this.b / this.a; - this.es = 1 - Math.pow(this.temp, 2); - this.e3 = Math.sqrt(this.es); + this.es = 1 - pow(this.temp, 2); + this.e3 = sqrt(this.es); - this.sin_po = Math.sin(this.lat1); - this.cos_po = Math.cos(this.lat1); + this.sin_po = sin(this.lat1); + this.cos_po = cos(this.lat1); this.t1 = this.sin_po; this.con = this.sin_po; this.ms1 = msfnz(this.e3, this.sin_po, this.cos_po); this.qs1 = qsfnz(this.e3, this.sin_po); - this.sin_po = Math.sin(this.lat2); - this.cos_po = Math.cos(this.lat2); + this.sin_po = sin(this.lat2); + this.cos_po = cos(this.lat2); this.t2 = this.sin_po; this.ms2 = msfnz(this.e3, this.sin_po, this.cos_po); this.qs2 = qsfnz(this.e3, this.sin_po); - this.sin_po = Math.sin(this.lat0); - this.cos_po = Math.cos(this.lat0); + this.sin_po = sin(this.lat0); + this.cos_po = cos(this.lat0); this.t3 = this.sin_po; this.qs0 = qsfnz(this.e3, this.sin_po); - if (Math.abs(this.lat1 - this.lat2) > EPSLN) { + if (abs(this.lat1 - this.lat2) > EPSLN) { this.ns0 = (this.ms1 * this.ms1 - this.ms2 * this.ms2) / (this.qs2 - this.qs1); } else { this.ns0 = this.con; } this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1; - this.rh = (this.a * Math.sqrt(this.c - this.ns0 * this.qs0)) / this.ns0; + this.rh = (this.a * sqrt(this.c - this.ns0 * this.qs0)) / this.ns0; } /** - * Albers Conical Equal Area forward equations--mapping lat,long to x,y + * Albers Conical Equal Area forward equations--mapping lon-lat to x-y * @param p - lon-lat WGS84 point * @returns - an Albers Conic Equal Area point */ @@ -76,13 +109,13 @@ export class AlbersConicEqualArea extends ProjectionTransform { const lon = p.x; const lat = p.y; - const sin_phi = Math.sin(lat); + const sin_phi = sin(lat); const qs = qsfnz(this.e3, sin_phi); - const rh1 = (this.a * Math.sqrt(this.c - this.ns0 * qs)) / this.ns0; + const rh1 = (this.a * sqrt(this.c - this.ns0 * qs)) / this.ns0; const theta = this.ns0 * adjustLon(lon - this.long0); - const x = rh1 * Math.sin(theta) + this.x0; - const y = this.rh - rh1 * Math.cos(theta) + this.y0; + const x = rh1 * sin(theta) + this.x0; + const y = this.rh - rh1 * cos(theta) + this.y0; p.x = x; p.y = y; @@ -90,6 +123,7 @@ export class AlbersConicEqualArea extends ProjectionTransform { } /** + * Albers Conical Equal Area inverse equations--mapping x-y to lon-lat * @param p - Albers Conic Equal Area point * @returns - lon-lat WGS84 point */ @@ -100,19 +134,19 @@ export class AlbersConicEqualArea extends ProjectionTransform { p.x -= this.x0; p.y = this.rh - p.y + this.y0; if (this.ns0 >= 0) { - rh1 = Math.sqrt(p.x * p.x + p.y * p.y); + rh1 = sqrt(p.x * p.x + p.y * p.y); con = 1; } else { - rh1 = -Math.sqrt(p.x * p.x + p.y * p.y); + rh1 = -sqrt(p.x * p.x + p.y * p.y); con = -1; } theta = 0; if (rh1 !== 0) { - theta = Math.atan2(con * p.x, con * p.y); + theta = atan2(con * p.x, con * p.y); } con = (rh1 * this.ns0) / this.a; if (this.sphere) { - lat = Math.asin((this.c - con * con) / (2 * this.ns0)); + lat = asin((this.c - con * con) / (2 * this.ns0)); } else { qs = (this.c - con * con) / this.ns0; lat = phi1z(this.e3, qs); @@ -140,15 +174,15 @@ export function phi1z(eccent: number, qs: number): number { const eccnts = eccent * eccent; for (let i = 1; i <= 25; i++) { - sinphi = Math.sin(phi); - cosphi = Math.cos(phi); + sinphi = sin(phi); + cosphi = cos(phi); con = eccent * sinphi; com = 1 - con * con; dphi = ((0.5 * com * com) / cosphi) * - (qs / (1 - eccnts) - sinphi / com + (0.5 / eccent) * Math.log((1 - con) / (1 + con))); + (qs / (1 - eccnts) - sinphi / com + (0.5 / eccent) * log((1 - con) / (1 + con))); phi = phi + dphi; - if (Math.abs(dphi) <= 1e-7) { + if (abs(dphi) <= 1e-7) { return phi; } } diff --git a/src/proj4/projections/aeqd.ts b/src/proj4/projections/aeqd.ts index 9c22ae47..d5b649e6 100644 --- a/src/proj4/projections/aeqd.ts +++ b/src/proj4/projections/aeqd.ts @@ -1,35 +1,75 @@ -import { ProjectionTransform } from '.'; +import { WGS84Projection } from '.'; import { EPSLN, HALF_PI } from '../constants'; import { adjustLon, asinz, e0fn, e1fn, e2fn, e3fn, gN, imlfn, mlfn } from '../common'; -import type { VectorPoint } from 's2json-spec'; +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const { abs, pow, sin, cos, sqrt, atan2, asin, acos, PI, tan, atan } = Math; /** + * # Azimuthal Equidistant Projection + * + * **Classification**: Azimuthal + * + * **Available forms**: Forward and inverse, spherical and ellipsoidal + * + * **Defined area**: Global + * + * **Alias**: `aeqd` + * + * **Domain**: 2D + * + * **Input type**: Geodetic coordinates * + * **Output type**: Projected coordinates + * + * ## Projection String + * ``` + * +proj=aeqd + * ``` + * + * ## Required Parameters + * None + * + * ## Optional Parameters + * - `guam`: Use Guam ellipsoidal formulas (accurate near Guam: λ ≈ 144.5°, φ ≈ 13.5°) + * - `lat0`: Latitude of origin + * - `lon0`: Longitude of origin + * - `x0`: False easting + * - `y0`: False northing + * - `ellps`: Ellipsoid name + * - `R`: Radius of sphere + * + * ![Azimuthal Equidistant Projection](https://github.com/OSGeo/PROJ/blob/38dd7c2446f3500a43f0257f5a4833d6aa5aab0b/docs/source/operations/projections/images/aeqd.png?raw=true) */ -export class AzimuthalEquidistantProjection extends ProjectionTransform { +export class AzimuthalEquidistantProjection + extends WGS84Projection + implements ProjectionTransformDefinition +{ name = 'Azimuthal_Equidistant'; names = [this.name, 'aeqd']; // AzimuthalEquidistantProjection specific variables - sin_p12: number; - cos_p12: number; - /** - * @param srsCode - */ - constructor(srsCode?: string) { - super(srsCode); - this.sin_p12 = Math.sin(this.lat0); - this.cos_p12 = Math.cos(this.lat0); + sin_p12 = 0; + cos_p12 = 0; + + /** Preps an Albers Conic Equal Area projection */ + constructor() { + super(); + this.sin_p12 = sin(this.lat0); + this.cos_p12 = cos(this.lat0); } /** - * @param p + * Azimuthal Equidistant forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - an Azimuthal Equidistant point */ forward(p: VectorPoint): VectorPoint { const lon = p.x; const lat = p.y; - const sinphi = Math.sin(p.y); - const cosphi = Math.cos(p.y); + const sinphi = sin(p.y); + const cosphi = cos(p.y); const dlon = adjustLon(lon - this.long0); let e0, e1, @@ -55,24 +95,23 @@ export class AzimuthalEquidistantProjection extends ProjectionTransform { s4, s5; if (this.sphere) { - if (Math.abs(this.sin_p12 - 1) <= EPSLN) { + if (abs(this.sin_p12 - 1) <= EPSLN) { //North Pole case - p.x = this.x0 + this.a * (HALF_PI - lat) * Math.sin(dlon); - p.y = this.y0 - this.a * (HALF_PI - lat) * Math.cos(dlon); + p.x = this.x0 + this.a * (HALF_PI - lat) * sin(dlon); + p.y = this.y0 - this.a * (HALF_PI - lat) * cos(dlon); return p; - } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { + } else if (abs(this.sin_p12 + 1) <= EPSLN) { //South Pole case - p.x = this.x0 + this.a * (HALF_PI + lat) * Math.sin(dlon); - p.y = this.y0 + this.a * (HALF_PI + lat) * Math.cos(dlon); + p.x = this.x0 + this.a * (HALF_PI + lat) * sin(dlon); + p.y = this.y0 + this.a * (HALF_PI + lat) * cos(dlon); return p; } else { //default case - cos_c = this.sin_p12 * sinphi + this.cos_p12 * cosphi * Math.cos(dlon); - c = Math.acos(cos_c); - kp = c ? c / Math.sin(c) : 1; - p.x = this.x0 + this.a * kp * cosphi * Math.sin(dlon); - p.y = - this.y0 + this.a * kp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * Math.cos(dlon)); + cos_c = this.sin_p12 * sinphi + this.cos_p12 * cosphi * cos(dlon); + c = acos(cos_c); + kp = c ? c / sin(c) : 1; + p.x = this.x0 + this.a * kp * cosphi * sin(dlon); + p.y = this.y0 + this.a * kp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * cos(dlon)); return p; } } else { @@ -80,39 +119,36 @@ export class AzimuthalEquidistantProjection extends ProjectionTransform { e1 = e1fn(this.es); e2 = e2fn(this.es); e3 = e3fn(this.es); - if (Math.abs(this.sin_p12 - 1) <= EPSLN) { + if (abs(this.sin_p12 - 1) <= EPSLN) { //North Pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); Ml = this.a * mlfn(e0, e1, e2, e3, lat); - p.x = this.x0 + (Mlp - Ml) * Math.sin(dlon); - p.y = this.y0 - (Mlp - Ml) * Math.cos(dlon); + p.x = this.x0 + (Mlp - Ml) * sin(dlon); + p.y = this.y0 - (Mlp - Ml) * cos(dlon); return p; - } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { + } else if (abs(this.sin_p12 + 1) <= EPSLN) { //South Pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); Ml = this.a * mlfn(e0, e1, e2, e3, lat); - p.x = this.x0 + (Mlp + Ml) * Math.sin(dlon); - p.y = this.y0 + (Mlp + Ml) * Math.cos(dlon); + p.x = this.x0 + (Mlp + Ml) * sin(dlon); + p.y = this.y0 + (Mlp + Ml) * cos(dlon); return p; } else { //Default case tanphi = sinphi / cosphi; Nl1 = gN(this.a, this.e, this.sin_p12); Nl = gN(this.a, this.e, sinphi); - psi = Math.atan((1 - this.es) * tanphi + (this.es * Nl1 * this.sin_p12) / (Nl * cosphi)); - Az = Math.atan2( - Math.sin(dlon), - this.cos_p12 * Math.tan(psi) - this.sin_p12 * Math.cos(dlon), - ); + psi = atan((1 - this.es) * tanphi + (this.es * Nl1 * this.sin_p12) / (Nl * cosphi)); + Az = atan2(sin(dlon), this.cos_p12 * tan(psi) - this.sin_p12 * cos(dlon)); if (Az === 0) { - s = Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi)); - } else if (Math.abs(Math.abs(Az) - Math.PI) <= EPSLN) { - s = -Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi)); + s = asin(this.cos_p12 * sin(psi) - this.sin_p12 * cos(psi)); + } else if (abs(abs(Az) - PI) <= EPSLN) { + s = -asin(this.cos_p12 * sin(psi) - this.sin_p12 * cos(psi)); } else { - s = Math.asin((Math.sin(dlon) * Math.cos(psi)) / Math.sin(Az)); + s = asin((sin(dlon) * cos(psi)) / sin(Az)); } - G = (this.e * this.sin_p12) / Math.sqrt(1 - this.es); - H = (this.e * this.cos_p12 * Math.cos(Az)) / Math.sqrt(1 - this.es); + G = (this.e * this.sin_p12) / sqrt(1 - this.es); + H = (this.e * this.cos_p12 * cos(Az)) / sqrt(1 - this.es); GH = G * H; Hs = H * H; s2 = s * s; @@ -127,15 +163,17 @@ export class AzimuthalEquidistantProjection extends ProjectionTransform { (s3 / 8) * GH * (1 - 2 * Hs) + (s4 / 120) * (Hs * (4 - 7 * Hs) - 3 * G * G * (1 - 7 * Hs)) - (s5 / 48) * GH); - p.x = this.x0 + c * Math.sin(Az); - p.y = this.y0 + c * Math.cos(Az); + p.x = this.x0 + c * sin(Az); + p.y = this.y0 + c * cos(Az); return p; } } } /** - * @param p + * Azimuthal Equidistant inverse equations--mapping x-y to lon-lat + * @param p - Azimuthal Equidistant point + * @returns - lon-lat WGS84 point */ inverse(p: VectorPoint): VectorPoint { p.x -= this.x0; @@ -165,38 +203,37 @@ export class AzimuthalEquidistantProjection extends ProjectionTransform { F, sinpsi; if (this.sphere) { - rh = Math.sqrt(p.x * p.x + p.y * p.y); + rh = sqrt(p.x * p.x + p.y * p.y); if (rh > 2 * HALF_PI * this.a) { throw Error('Point is not in the projection'); } z = rh / this.a; - sinz = Math.sin(z); - cosz = Math.cos(z); + sinz = sin(z); + cosz = cos(z); lon = this.long0; - if (Math.abs(rh) <= EPSLN) { + if (abs(rh) <= EPSLN) { lat = this.lat0; } else { lat = asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh); - con = Math.abs(this.lat0) - HALF_PI; - if (Math.abs(con) <= EPSLN) { + con = abs(this.lat0) - HALF_PI; + if (abs(con) <= EPSLN) { if (this.lat0 >= 0) { - lon = adjustLon(this.long0 + Math.atan2(p.x, -p.y)); + lon = adjustLon(this.long0 + atan2(p.x, -p.y)); } else { - lon = adjustLon(this.long0 - Math.atan2(-p.x, p.y)); + lon = adjustLon(this.long0 - atan2(-p.x, p.y)); } } else { - /*con = cosz - this.sin_p12 * Math.sin(lat); - if ((Math.abs(con) < EPSLN) && (Math.abs(p.x) < EPSLN)) { + /*con = cosz - this.sin_p12 * sin(lat); + if ((abs(con) < EPSLN) && (abs(p.x) < EPSLN)) { //no-op, just keep the lon value as is } else { - var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh)); - lon = adjustLon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh))); + var temp = atan2((p.x * sinz * this.cos_p12), (con * rh)); + lon = adjustLon(this.long0 + atan2((p.x * sinz * this.cos_p12), (con * rh))); }*/ lon = adjustLon( - this.long0 + - Math.atan2(p.x * sinz, rh * this.cos_p12 * cosz - p.y * this.sin_p12 * sinz), + this.long0 + atan2(p.x * sinz, rh * this.cos_p12 * cosz - p.y * this.sin_p12 * sinz), ); } } @@ -209,46 +246,43 @@ export class AzimuthalEquidistantProjection extends ProjectionTransform { e1 = e1fn(this.es); e2 = e2fn(this.es); e3 = e3fn(this.es); - if (Math.abs(this.sin_p12 - 1) <= EPSLN) { + if (abs(this.sin_p12 - 1) <= EPSLN) { //North pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); - rh = Math.sqrt(p.x * p.x + p.y * p.y); + rh = sqrt(p.x * p.x + p.y * p.y); M = Mlp - rh; lat = imlfn(M / this.a, e0, e1, e2, e3); - lon = adjustLon(this.long0 + Math.atan2(p.x, -1 * p.y)); + lon = adjustLon(this.long0 + atan2(p.x, -1 * p.y)); p.x = lon; p.y = lat; return p; - } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { + } else if (abs(this.sin_p12 + 1) <= EPSLN) { //South pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); - rh = Math.sqrt(p.x * p.x + p.y * p.y); + rh = sqrt(p.x * p.x + p.y * p.y); M = rh - Mlp; lat = imlfn(M / this.a, e0, e1, e2, e3); - lon = adjustLon(this.long0 + Math.atan2(p.x, p.y)); + lon = adjustLon(this.long0 + atan2(p.x, p.y)); p.x = lon; p.y = lat; return p; } else { //default case - rh = Math.sqrt(p.x * p.x + p.y * p.y); - Az = Math.atan2(p.x, p.y); + rh = sqrt(p.x * p.x + p.y * p.y); + Az = atan2(p.x, p.y); N1 = gN(this.a, this.e, this.sin_p12); - cosAz = Math.cos(Az); + cosAz = cos(Az); tmp = this.e * this.cos_p12 * cosAz; A = (-tmp * tmp) / (1 - this.es); B = (3 * this.es * (1 - A) * this.sin_p12 * this.cos_p12 * cosAz) / (1 - this.es); D = rh / N1; - Ee = D - (A * (1 + A) * Math.pow(D, 3)) / 6 - (B * (1 + 3 * A) * Math.pow(D, 4)) / 24; + Ee = D - (A * (1 + A) * pow(D, 3)) / 6 - (B * (1 + 3 * A) * pow(D, 4)) / 24; F = 1 - (A * Ee * Ee) / 2 - (D * Ee * Ee * Ee) / 6; - psi = Math.asin(this.sin_p12 * Math.cos(Ee) + this.cos_p12 * Math.sin(Ee) * cosAz); - lon = adjustLon(this.long0 + Math.asin((Math.sin(Az) * Math.sin(Ee)) / Math.cos(psi))); - sinpsi = Math.sin(psi); - lat = Math.atan2( - (sinpsi - this.es * F * this.sin_p12) * Math.tan(psi), - sinpsi * (1 - this.es), - ); + psi = asin(this.sin_p12 * cos(Ee) + this.cos_p12 * sin(Ee) * cosAz); + lon = adjustLon(this.long0 + asin((sin(Az) * sin(Ee)) / cos(psi))); + sinpsi = sin(psi); + lat = atan2((sinpsi - this.es * F * this.sin_p12) * tan(psi), sinpsi * (1 - this.es)); p.x = lon; p.y = lat; return p; diff --git a/src/proj4/projections/bonne.js b/src/proj4/projections/bonne.js deleted file mode 100644 index b9372be2..00000000 --- a/src/proj4/projections/bonne.js +++ /dev/null @@ -1,113 +0,0 @@ -import adjust_lat from "../common/adjust_lat"; -import adjust_lon from "../common/adjust_lon"; -import hypot from "../common/hypot"; -import pj_enfn from "../common/pj_enfn"; -import pj_inv_mlfn from "../common/pj_inv_mlfn"; -import pj_mlfn from "../common/pj_mlfn"; -import { HALF_PI } from "../constants/values"; - -var EPS10 = 1e-10; - -export function init() { - var c; - - this.phi1 = this.lat1; - if (Math.abs(this.phi1) < EPS10) { - throw new Error('Invalid latitude'); - } - if (this.es) { - this.en = pj_enfn(this.es); - this.m1 = pj_mlfn(this.phi1, this.am1 = Math.sin(this.phi1), - c = Math.cos(this.phi1), this.en); - this.am1 = c / (Math.sqrt(1 - this.es * this.am1 * this.am1) * this.am1); - this.inverse = e_inv; - this.forward = e_fwd; - } else { - if (Math.abs(this.phi1) + EPS10 >= HALF_PI) { - this.cphi1 = 0; - } - else { - this.cphi1 = 1 / Math.tan(this.phi1); - } - this.inverse = s_inv; - this.forward = s_fwd; - } -} - -function e_fwd(p) { - var lam = adjust_lon(p.x - (this.long0 || 0)); - var phi = p.y; - var rh, E, c; - rh = this.am1 + this.m1 - pj_mlfn(phi, E = Math.sin(phi), c = Math.cos(phi), this.en); - E = c * lam / (rh * Math.sqrt(1 - this.es * E * E)); - p.x = rh * Math.sin(E); - p.y = this.am1 - rh * Math.cos(E); - - p.x = this.a * p.x + (this.x0 || 0); - p.y = this.a * p.y + (this.y0 || 0); - return p; -} - -function e_inv(p) { - p.x = (p.x - (this.x0 || 0)) / this.a; - p.y = (p.y - (this.y0 || 0)) / this.a; - - var s, rh, lam, phi; - rh = hypot(p.x, p.y = this.am1 - p.y); - phi = pj_inv_mlfn(this.am1 + this.m1 - rh, this.es, this.en); - if ((s = Math.abs(phi)) < HALF_PI) { - s = Math.sin(phi); - lam = rh * Math.atan2(p.x, p.y) * Math.sqrt(1 - this.es * s * s) / Math.cos(phi); - } else if (Math.abs(s - HALF_PI) <= EPS10) { - lam = 0; - } - else { - throw new Error(); - } - p.x = adjust_lon(lam + (this.long0 || 0)); - p.y = adjust_lat(phi); - return p; -} - -function s_fwd(p) { - var lam = adjust_lon(p.x - (this.long0 || 0)); - var phi = p.y; - var E, rh; - rh = this.cphi1 + this.phi1 - phi; - if (Math.abs(rh) > EPS10) { - p.x = rh * Math.sin(E = lam * Math.cos(phi) / rh); - p.y = this.cphi1 - rh * Math.cos(E); - } else { - p.x = p.y = 0; - } - - p.x = this.a * p.x + (this.x0 || 0); - p.y = this.a * p.y + (this.y0 || 0); - return p; -} - -function s_inv(p) { - p.x = (p.x - (this.x0 || 0)) / this.a; - p.y = (p.y - (this.y0 || 0)) / this.a; - - var lam, phi; - var rh = hypot(p.x, p.y = this.cphi1 - p.y); - phi = this.cphi1 + this.phi1 - rh; - if (Math.abs(phi) > HALF_PI) { - throw new Error(); - } - if (Math.abs(Math.abs(phi) - HALF_PI) <= EPS10) { - lam = 0; - } else { - lam = rh * Math.atan2(p.x, p.y) / Math.cos(phi); - } - p.x = adjust_lon(lam + (this.long0 || 0)); - p.y = adjust_lat(phi); - return p; -} - -export var names = ["bonne", "Bonne (Werner lat_1=90)"]; -export default { - init: init, - names: names -}; \ No newline at end of file diff --git a/src/proj4/projections/bonne.ts b/src/proj4/projections/bonne.ts new file mode 100644 index 00000000..f8b4d968 --- /dev/null +++ b/src/proj4/projections/bonne.ts @@ -0,0 +1,175 @@ +import { HALF_PI } from '../constants'; +import { WGS84Projection } from '.'; +import { adjustLat, adjustLon, hypot, pjEnfn, pjInvMlfn, pjMlfn } from '../common'; + +import type { En } from '../common'; +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const EPS10 = 1e-10; +const { abs, sin, cos, sqrt, atan2, tan } = Math; + +/** + * # Bonne (Werner lat_1=90) Projection + * + * **Classification**: Miscellaneous + * + * **Available forms**: Forward and inverse, spherical and ellipsoidal + * + * **Defined area**: Global + * + * **Alias**: `bonne` + * + * **Domain**: 2D + * + * **Input type**: Geodetic coordinates + * + * **Output type**: Projected coordinates + * + * ## Projection String + * ``` + * +proj=bonne +lat_1=10 + * ``` + * + * ## Required Parameters + * - `lat1`: Latitude of first standard parallel + * + * ## Optional Parameters + * - `lon0`: Longitude of origin + * - `ellps`: Ellipsoid name + * - `R`: Radius of sphere + * - `x0`: False easting + * - `y0`: False northing + * + * ![Bonne (Werner lat_1=90) Projection](https://github.com/OSGeo/PROJ/blob/38dd7c2446f3500a43f0257f5a4833d6aa5aab0b/docs/source/operations/projections/images/bonne.png?raw=true) + */ +export class BonneWernerProjection + extends WGS84Projection + implements ProjectionTransformDefinition +{ + name = 'Bonne (Werner lat_1=90)'; + names = [this.name, 'bonne']; + // BonneWernerProjection specific variables + phi1 = 0; + en: En = [0, 0, 0, 0, 0]; + m1: number = 0; + am1: number = 0; + cphi1: number = 0; + + /** Preps an Albers Conic Equal Area projection */ + constructor() { + super(); + let c; + + this.phi1 = this.lat1; + if (abs(this.phi1) < EPS10) { + throw new Error('Invalid latitude'); + } + if (this.es) { + this.en = pjEnfn(this.es); + this.m1 = pjMlfn(this.phi1, (this.am1 = sin(this.phi1)), (c = cos(this.phi1)), this.en); + this.am1 = c / (sqrt(1 - this.es * this.am1 * this.am1) * this.am1); + this.inverse = this.eInv; + this.forward = this.eFwd; + } else { + if (abs(this.phi1) + EPS10 >= HALF_PI) { + this.cphi1 = 0; + } else { + this.cphi1 = 1 / tan(this.phi1); + } + this.inverse = this.sInv; + this.forward = this.sFwd; + } + } + + /** + * Bonne Werner Easting forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - an Bonne Werner point + */ + eFwd(p: VectorPoint): VectorPoint { + const lam = adjustLon(p.x - (this.long0 || 0)); + const phi = p.y; + let E, c; + const rh = this.am1 + this.m1 - pjMlfn(phi, (E = sin(phi)), (c = cos(phi)), this.en); + E = (c * lam) / (rh * sqrt(1 - this.es * E * E)); + p.x = rh * sin(E); + p.y = this.am1 - rh * cos(E); + + p.x = this.a * p.x + (this.x0 || 0); + p.y = this.a * p.y + (this.y0 || 0); + return p; + } + + /** + * Bonne Werner Easting inverse equations--mapping x-y to lon-lat + * @param p - Bonne Werner point + * @returns - lon-lat WGS84 point + */ + eInv(p: VectorPoint): VectorPoint { + p.x = (p.x - (this.x0 || 0)) / this.a; + p.y = (p.y - (this.y0 || 0)) / this.a; + + let s: number, lam: number; + const rh = hypot(p.x, (p.y = this.am1 - p.y)); + const phi = pjInvMlfn(this.am1 + this.m1 - rh, this.es, this.en); + if ((s = abs(phi)) < HALF_PI) { + s = sin(phi); + lam = (rh * atan2(p.x, p.y) * sqrt(1 - this.es * s * s)) / cos(phi); + } else if (abs(s - HALF_PI) <= EPS10) { + lam = 0; + } else { + throw new Error(); + } + p.x = adjustLon(lam + (this.long0 || 0)); + p.y = adjustLat(phi); + return p; + } + + /** + * Bonne Werner Southing forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - an Bonne Werner point + */ + sFwd(p: VectorPoint): VectorPoint { + const lam = adjustLon(p.x - (this.long0 || 0)); + const phi = p.y; + let E: number; + const rh = this.cphi1 + this.phi1 - phi; + if (abs(rh) > EPS10) { + p.x = rh * sin((E = (lam * cos(phi)) / rh)); + p.y = this.cphi1 - rh * cos(E); + } else { + p.x = p.y = 0; + } + + p.x = this.a * p.x + (this.x0 || 0); + p.y = this.a * p.y + (this.y0 || 0); + return p; + } + + /** + * Bonne Werner Southing inverse equations--mapping x-y to lon-lat + * @param p - Bonne Werner point + * @returns - lon-lat WGS84 point + */ + sInv(p: VectorPoint): VectorPoint { + p.x = (p.x - (this.x0 || 0)) / this.a; + p.y = (p.y - (this.y0 || 0)) / this.a; + + let lam: number; + const rh = hypot(p.x, (p.y = this.cphi1 - p.y)); + const phi = this.cphi1 + this.phi1 - rh; + if (abs(phi) > HALF_PI) { + throw new Error(); + } + if (abs(abs(phi) - HALF_PI) <= EPS10) { + lam = 0; + } else { + lam = (rh * atan2(p.x, p.y)) / cos(phi); + } + p.x = adjustLon(lam + (this.long0 || 0)); + p.y = adjustLat(phi); + return p; + } +} diff --git a/src/proj4/projections/cass.js b/src/proj4/projections/cass.js deleted file mode 100644 index 74f4d388..00000000 --- a/src/proj4/projections/cass.js +++ /dev/null @@ -1,108 +0,0 @@ -import mlfn from '../common/mlfn'; -import e0fn from '../common/e0fn'; -import e1fn from '../common/e1fn'; -import e2fn from '../common/e2fn'; -import e3fn from '../common/e3fn'; -import gN from '../common/gN'; -import adjust_lon from '../common/adjust_lon'; -import adjust_lat from '../common/adjust_lat'; -import imlfn from '../common/imlfn'; -import {HALF_PI, EPSLN} from '../constants/values'; - -export function init() { - if (!this.sphere) { - this.e0 = e0fn(this.es); - this.e1 = e1fn(this.es); - this.e2 = e2fn(this.es); - this.e3 = e3fn(this.es); - this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); - } -} - -/* Cassini forward equations--mapping lat,long to x,y - -----------------------------------------------------------------------*/ -export function forward(p) { - - /* Forward equations - -----------------*/ - var x, y; - var lam = p.x; - var phi = p.y; - lam = adjust_lon(lam - this.long0); - - if (this.sphere) { - x = this.a * Math.asin(Math.cos(phi) * Math.sin(lam)); - y = this.a * (Math.atan2(Math.tan(phi), Math.cos(lam)) - this.lat0); - } - else { - //ellipsoid - var sinphi = Math.sin(phi); - var cosphi = Math.cos(phi); - var nl = gN(this.a, this.e, sinphi); - var tl = Math.tan(phi) * Math.tan(phi); - var al = lam * Math.cos(phi); - var asq = al * al; - var cl = this.es * cosphi * cosphi / (1 - this.es); - var ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi); - - x = nl * al * (1 - asq * tl * (1 / 6 - (8 - tl + 8 * cl) * asq / 120)); - y = ml - this.ml0 + nl * sinphi / cosphi * asq * (0.5 + (5 - tl + 6 * cl) * asq / 24); - - - } - - p.x = x + this.x0; - p.y = y + this.y0; - return p; -} - -/* Inverse equations - -----------------*/ -export function inverse(p) { - p.x -= this.x0; - p.y -= this.y0; - var x = p.x / this.a; - var y = p.y / this.a; - var phi, lam; - - if (this.sphere) { - var dd = y + this.lat0; - phi = Math.asin(Math.sin(dd) * Math.cos(x)); - lam = Math.atan2(Math.tan(x), Math.cos(dd)); - } - else { - /* ellipsoid */ - var ml1 = this.ml0 / this.a + y; - var phi1 = imlfn(ml1, this.e0, this.e1, this.e2, this.e3); - if (Math.abs(Math.abs(phi1) - HALF_PI) <= EPSLN) { - p.x = this.long0; - p.y = HALF_PI; - if (y < 0) { - p.y *= -1; - } - return p; - } - var nl1 = gN(this.a, this.e, Math.sin(phi1)); - - var rl1 = nl1 * nl1 * nl1 / this.a / this.a * (1 - this.es); - var tl1 = Math.pow(Math.tan(phi1), 2); - var dl = x * this.a / nl1; - var dsq = dl * dl; - phi = phi1 - nl1 * Math.tan(phi1) / rl1 * dl * dl * (0.5 - (1 + 3 * tl1) * dl * dl / 24); - lam = dl * (1 - dsq * (tl1 / 3 + (1 + 3 * tl1) * tl1 * dsq / 15)) / Math.cos(phi1); - - } - - p.x = adjust_lon(lam + this.long0); - p.y = adjust_lat(phi); - return p; - -} - -export var names = ["Cassini", "Cassini_Soldner", "cass"]; -export default { - init: init, - forward: forward, - inverse: inverse, - names: names -}; diff --git a/src/proj4/projections/cass.ts b/src/proj4/projections/cass.ts new file mode 100644 index 00000000..c22fd69d --- /dev/null +++ b/src/proj4/projections/cass.ts @@ -0,0 +1,149 @@ +import { WGS84Projection } from '.'; +import { EPSLN, HALF_PI } from '../constants'; +import { adjustLat, adjustLon, e0fn, e1fn, e2fn, e3fn, gN, imlfn, mlfn } from '../common'; + +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const { abs, sin, cos, asin, atan2, tan, pow } = Math; + +/** + * # Cassini (Cassini-Soldner) Projection + * + * Although the Cassini projection has been largely replaced by the Transverse Mercator, it is + * still in limited use outside the United States and was one of the major topographic mapping + * projections until the early 20th century. + * + * **Classification**: Transverse and oblique cylindrical + * + * **Available forms**: Forward and Inverse, Spherical and ellipsoidal + * + * **Defined area**: Global, but best used near the central meridian with long, narrow areas + * + * **Alias**: `cass` + * + * **Domain**: 2D + * + * **Input type**: Geodetic coordinates + * + * **Output type**: Projected coordinates + * + * ## Projection String + * ``` + * +proj=cass + * ``` + * + * ## Required Parameters + * - `lat_0` + * + * ## Optional Parameters + * - `lon_0` + * - `x_0` + * - `y_0` + * - `ellps` + * - `R` + * - `+hyperbolic`: Use modified form of the standard Cassini-Soldner projection known as the Hyperbolic Cassini-Soldner (used in EPSG:3139). + * + * ![Cassini (Cassini-Soldner)](./images/cass.png) + */ +export class CassiniSoldnerProjection + extends WGS84Projection + implements ProjectionTransformDefinition +{ + name = 'Cassini_Soldner'; + names = [this.name, 'Cassini', 'cass']; + e0: number = 0; + e1: number = 0; + e2: number = 0; + e3: number = 0; + ml0: number = 0; + + /** Preps an Cassini Soldner projection */ + constructor() { + super(); + if (this.sphere === undefined) { + this.e0 = e0fn(this.es); + this.e1 = e1fn(this.es); + this.e2 = e2fn(this.es); + this.e3 = e3fn(this.es); + this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); + } + } + + /** + * Cassini Soldner forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - a Cassini Soldner point + */ + forward(p: VectorPoint): VectorPoint { + let x, y; + let lam = p.x; + const phi = p.y; + lam = adjustLon(lam - this.long0); + + if (this.sphere) { + x = this.a * asin(cos(phi) * sin(lam)); + y = this.a * (atan2(tan(phi), cos(lam)) - this.lat0); + } else { + //ellipsoid + const sinphi = sin(phi); + const cosphi = cos(phi); + const nl = gN(this.a, this.e, sinphi); + const tl = tan(phi) * tan(phi); + const al = lam * cos(phi); + const asq = al * al; + const cl = (this.es * cosphi * cosphi) / (1 - this.es); + const ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi); + + x = nl * al * (1 - asq * tl * (1 / 6 - ((8 - tl + 8 * cl) * asq) / 120)); + y = ml - this.ml0 + ((nl * sinphi) / cosphi) * asq * (0.5 + ((5 - tl + 6 * cl) * asq) / 24); + } + + p.x = x + this.x0; + p.y = y + this.y0; + return p; + } + + /** + * Cassini Soldner inverse equations--mapping x-y to lon-lat + * @param p - A Cassini Soldner point + * @returns - lon-lat WGS84 point + */ + inverse(p: VectorPoint): VectorPoint { + p.x -= this.x0; + p.y -= this.y0; + const x = p.x / this.a; + const y = p.y / this.a; + let phi, lam; + + if (this.sphere) { + const dd = y + this.lat0; + phi = asin(sin(dd) * cos(x)); + lam = atan2(tan(x), cos(dd)); + } else { + /* ellipsoid */ + const ml1 = this.ml0 / this.a + y; + const phi1 = imlfn(ml1, this.e0, this.e1, this.e2, this.e3); + if (abs(abs(phi1) - HALF_PI) <= EPSLN) { + p.x = this.long0; + p.y = HALF_PI; + if (y < 0) { + p.y *= -1; + } + return p; + } + const nl1 = gN(this.a, this.e, sin(phi1)); + + const rl1 = ((nl1 * nl1 * nl1) / this.a / this.a) * (1 - this.es); + const tl1 = pow(tan(phi1), 2); + const dl = (x * this.a) / nl1; + const dsq = dl * dl; + phi = phi1 - ((nl1 * tan(phi1)) / rl1) * dl * dl * (0.5 - ((1 + 3 * tl1) * dl * dl) / 24); + lam = (dl * (1 - dsq * (tl1 / 3 + ((1 + 3 * tl1) * tl1 * dsq) / 15))) / cos(phi1); + } + + p.x = adjustLon(lam + this.long0); + p.y = adjustLat(phi); + return p; + } +} diff --git a/src/proj4/projections/cea.js b/src/proj4/projections/cea.js deleted file mode 100644 index 68fc2adb..00000000 --- a/src/proj4/projections/cea.js +++ /dev/null @@ -1,70 +0,0 @@ -import adjust_lon from '../common/adjust_lon'; -import qsfnz from '../common/qsfnz'; -import msfnz from '../common/msfnz'; -import iqsfnz from '../common/iqsfnz'; - -/* - reference: - "Cartographic Projection Procedures for the UNIX Environment- - A User's Manual" by Gerald I. Evenden, - USGS Open File Report 90-284and Release 4 Interim Reports (2003) -*/ -export function init() { - //no-op - if (!this.sphere) { - this.k0 = msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts)); - } -} - -/* Cylindrical Equal Area forward equations--mapping lat,long to x,y - ------------------------------------------------------------*/ -export function forward(p) { - var lon = p.x; - var lat = p.y; - var x, y; - /* Forward equations - -----------------*/ - var dlon = adjust_lon(lon - this.long0); - if (this.sphere) { - x = this.x0 + this.a * dlon * Math.cos(this.lat_ts); - y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts); - } - else { - var qs = qsfnz(this.e, Math.sin(lat)); - x = this.x0 + this.a * this.k0 * dlon; - y = this.y0 + this.a * qs * 0.5 / this.k0; - } - - p.x = x; - p.y = y; - return p; -} - -/* Cylindrical Equal Area inverse equations--mapping x,y to lat/long - ------------------------------------------------------------*/ -export function inverse(p) { - p.x -= this.x0; - p.y -= this.y0; - var lon, lat; - - if (this.sphere) { - lon = adjust_lon(this.long0 + (p.x / this.a) / Math.cos(this.lat_ts)); - lat = Math.asin((p.y / this.a) * Math.cos(this.lat_ts)); - } - else { - lat = iqsfnz(this.e, 2 * p.y * this.k0 / this.a); - lon = adjust_lon(this.long0 + p.x / (this.a * this.k0)); - } - - p.x = lon; - p.y = lat; - return p; -} - -export var names = ["cea"]; -export default { - init: init, - forward: forward, - inverse: inverse, - names: names -}; diff --git a/src/proj4/projections/cea.ts b/src/proj4/projections/cea.ts new file mode 100644 index 00000000..c84689f9 --- /dev/null +++ b/src/proj4/projections/cea.ts @@ -0,0 +1,125 @@ +import { WGS84Projection } from '.'; +import { adjustLon, iqsfnz, msfnz, qsfnz } from '../common'; + +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const { sin, cos, asin } = Math; + +/** + * # Equal Area Cylindrical Projection + * + * **Classification**: Cylindrical + * + * **Available forms**: Forward and Inverse, spherical and ellipsoidal + * + * **Defined area**: Global + * + * **Alias**: `cea` + * + * **Domain**: 2D + * + * **Input type**: Geodetic coordinates + * + * **Output type**: Projected coordinates + * + * ## Projection String + * ``` + * +proj=cea + * ``` + * + * ## Named Specializations + * + * The Equal Area Cylindrical projection is sometimes known under other names when instantiated with particular values of the `lat_ts` parameter: + * + * - **Lambert cylindrical equal-area**: `lat_ts = 0` + * - **Behrmann**: `lat_ts = 30` + * - **Gall-Peters**: `lat_ts = 45` + * + * ## Optional Parameters + * - `lat_ts`: Latitude of true scale + * - `lon_0`: Longitude of origin + * - `ellps`: Ellipsoid + * - `R`: Radius of the sphere + * - `k_0`: Scaling factor + * - `x_0`: False easting + * - `y_0`: False northing + * + * > **Note**: `lat_ts` and `k_0` are mutually exclusive. If `lat_ts` is specified, it is equivalent to setting `k_0` to: + * > + * > ``` + * > k_0 = cos(lat_ts) / sqrt(1 - e^2 * sin^2(lat_ts)) + * > ``` + * + * reference: + * "Cartographic Projection Procedures for the UNIX Environment- + * A User's Manual" by Gerald I. Evenden, + * USGS Open File Report 90-284and Release 4 Interim Reports (2003) + * + * ![Equal Area Cylindrical Projection](./images/cea.png) + */ +export class CylindricalEqualAreaProjection + extends WGS84Projection + implements ProjectionTransformDefinition +{ + name = 'Equal_Area_Cylindrical'; + names = [this.name, 'cea']; + // Equal Area Cylindrical specific variables + + /** Preps an Equal Area Cylindrical projection */ + constructor() { + super(); + if (this.sphere === undefined) { + this.k0 = msfnz(this.e, sin(this.latTs), cos(this.latTs)); + } + } + + /** + * Equal Area Cylindrical forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - Equal Area Cylindrical point + */ + forward(p: VectorPoint): VectorPoint { + const lon = p.x; + const lat = p.y; + let x, y; + /* Forward equations + -----------------*/ + const dlon = adjustLon(lon - this.long0); + if (this.sphere) { + x = this.x0 + this.a * dlon * cos(this.latTs); + y = this.y0 + (this.a * sin(lat)) / cos(this.latTs); + } else { + const qs = qsfnz(this.e, sin(lat)); + x = this.x0 + this.a * this.k0 * dlon; + y = this.y0 + (this.a * qs * 0.5) / this.k0; + } + + p.x = x; + p.y = y; + return p; + } + + /** + * Equal Area Cylindrical inverse equations--mapping x-y to lon-lat + * @param p - Equal Area Cylindrical point + * @returns - lon-lat WGS84 point + */ + inverse(p: VectorPoint): VectorPoint { + p.x -= this.x0; + p.y -= this.y0; + let lon, lat; + + if (this.sphere) { + lon = adjustLon(this.long0 + p.x / this.a / cos(this.latTs)); + lat = asin((p.y / this.a) * cos(this.latTs)); + } else { + lat = iqsfnz(this.e, (2 * p.y * this.k0) / this.a); + lon = adjustLon(this.long0 + p.x / (this.a * this.k0)); + } + + p.x = lon; + p.y = lat; + return p; + } +} diff --git a/src/proj4/projections/geocent.js b/src/proj4/projections/geocent.js index 8ceadf88..d501f0af 100644 --- a/src/proj4/projections/geocent.js +++ b/src/proj4/projections/geocent.js @@ -24,4 +24,4 @@ export default { forward: forward, inverse: inverse, names: names -}; \ No newline at end of file +}; diff --git a/src/proj4/projections/geos.js b/src/proj4/projections/geos.js index 9a440956..9fa70b9d 100644 --- a/src/proj4/projections/geos.js +++ b/src/proj4/projections/geos.js @@ -156,4 +156,3 @@ export default { inverse: inverse, names: names, }; - diff --git a/src/proj4/projections/index.ts b/src/proj4/projections/index.ts index d704c03d..c035736d 100644 --- a/src/proj4/projections/index.ts +++ b/src/proj4/projections/index.ts @@ -1,16 +1,29 @@ +import { AlbersConicEqualArea } from './aea'; +import { AzimuthalEquidistantProjection } from './aeqd'; +import { BonneWernerProjection } from './bonne'; import { FT_TO_M } from '../constants'; import type { VectorPoint } from 's2json-spec'; export * from './aea'; +export * from './aeqd'; +export * from './bonne'; -/** - * - */ -export class ProjectionTransform { - name: string = 'default'; +/** All projections need these parameters */ +export interface ProjectionTransformDefinition { + name: string; + names: string[]; + datum: string; + forward: (p: VectorPoint) => VectorPoint; + inverse: (p: VectorPoint) => VectorPoint; +} + +/** Base class for all projections */ +export class ProjectionTransform implements ProjectionTransformDefinition { + name: string = ''; names: string[] = []; datum = 'none'; + srsCode = ''; // these are all variables must have a default value across all projections lon0 = 0; lon1 = 0; @@ -25,10 +38,10 @@ export class ProjectionTransform { a = 0; b = 0; e = 0; - r = 0; + R = 0; x0 = 0; y0 = 0; - k0 = 0; + k0 = 1; rf = 0; ra = 0; es = 0; @@ -44,36 +57,87 @@ export class ProjectionTransform { axis = 'enu'; nadgrids = '@null'; sphere?: number; + ellps = 'wgs84'; // Ellipsoid name /** - * @param srsCode + * @param srsCode - projection code to transform from + * @param names - optionally update projection names */ - constructor(srsCode?: string) {} + constructor(srsCode?: string, names?: string[]) { + if (srsCode !== undefined) this.srsCode = srsCode; + if (names !== undefined && names.length > 0) { + this.names = names; + this.name = names[0]; + } + } /** - * @param p + * Forward projection from x-y to lon-lat. In this case, it is a no-op + * @param p - Vector Point. This is a placeholder for a lon-lat WGS84 point + * @returns - the point itself */ forward(p: VectorPoint): VectorPoint { - return p; + return { ...p }; } /** - * @param p + * Inverse projection from lon-lat to x-y. In this case, it is a no-op + * @param p - Vector Point. This is a placeholder for a lon-lat WGS84 point + * @returns - the point itself */ inverse(p: VectorPoint): VectorPoint { - return p; + return { ...p }; } } -/** - * - */ -export class LonLatProjection extends ProjectionTransform { - name = 'longlat'; - names = [this.name, 'identity']; - /** - * @param srsCode - */ - constructor(srsCode?: string) { - super(srsCode); +/** WGS84 projection */ +export class WGS84Projection extends ProjectionTransform implements ProjectionTransformDefinition { + /** Prepares a WGS84 projection read to convert to and from lon-lat */ + constructor() { + super('+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees', [ + 'WGS84', + 'EPSG:4326', + ]); } } + +/** NAD83 projection */ +export class NAD83Projection extends ProjectionTransform implements ProjectionTransformDefinition { + /** Prepares a NAD83 projection read to convert to and from lon-lat */ + constructor() { + super( + '+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees', + ['NAD83', 'EPSG:4269'], + ); + } +} + +/** PseudoMercator projection */ +export class PseudoMercatorProjection + extends ProjectionTransform + implements ProjectionTransformDefinition +{ + /** Prepares a PseudoMercator projection read to convert to and from lon-lat */ + constructor() { + super( + '+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs', + ['PseudoMercator', 'EPSG:3857', 'EPSG:3785', 'EPSG:900913', 'EPSG:102113', 'GOOGLE'], + ); + } +} + +/** Contains all projections */ +export const ALL_PROJECTIONS: ProjectionTransformDefinition[] = [ + new AlbersConicEqualArea(), + new AzimuthalEquidistantProjection(), + new BonneWernerProjection(), +]; + +/** + * Builds most commonly used projections + * @returns - an array of default projections + */ +export const DEFAULT_PROJECTIONS: [ + WGS_84: ProjectionTransformDefinition, + NAD_83: ProjectionTransformDefinition, + PSEUDO_MERCATOR: ProjectionTransformDefinition, +] = [new WGS84Projection(), new NAD83Projection(), new PseudoMercatorProjection()]; diff --git a/src/proj4/projections/template.ts b/src/proj4/projections/template.ts new file mode 100644 index 00000000..f2076444 --- /dev/null +++ b/src/proj4/projections/template.ts @@ -0,0 +1,36 @@ +import { EPSLN } from '../constants'; +import { WGS84Projection } from '.'; +import { adjustLat, adjustLon } from '../common'; + +import type { ProjectionTransformDefinition } from '.'; +import type { VectorPoint } from 's2-tools/geometry'; + +const { abs, pow, sin, cos, sqrt, atan2, asin, log } = Math; + +/** + * + */ +export class XXXXXXXXXXXX extends WGS84Projection implements ProjectionTransformDefinition { + name = 'XXXXXXXXXXXX'; + names = [this.name, 'XXXXXXXXXXXX', 'XXXXXXXXXXXX']; + // XXXXXXXXXXXX specific variables + + /** Preps an XXXXXXXXXXXX projection */ + constructor() { + super(); + } + + /** + * XXXXXXXXXXXX forward equations--mapping lon-lat to x-y + * @param p - lon-lat WGS84 point + * @returns - XXXXXXXXXXXX point + */ + forward(p: VectorPoint): VectorPoint {} + + /** + * XXXXXXXXXXXX inverse equations--mapping x-y to lon-lat + * @param p - XXXXXXXXXXXX point + * @returns - lon-lat WGS84 point + */ + inverse(p: VectorPoint): VectorPoint {} +} diff --git a/src/proj4/transform.js b/src/proj4/transform.js index bf09136f..e0b9f91c 100644 --- a/src/proj4/transform.js +++ b/src/proj4/transform.js @@ -1,5 +1,5 @@ -import {D2R, R2D, PJD_3PARAM, PJD_7PARAM, PJD_GRIDSHIFT} from './constants/values'; -import datum_transform from '../constants/datum_transform'; +import {D2R, R2D, PJD_3PARAM, PJD_7PARAM, PJD_GRIDSHIFT} from './constants'; +import datum_transform from '../constants/datum'; import proj from './Proj'; import toPoint from './common/toPoint'; import { sanityCheck } from './Point'; @@ -33,7 +33,7 @@ export default function transform(source, dest, point, enforceAxis) { } // DGR, 2010/11/12 if (enforceAxis && source.axis !== 'enu') { - point = adjust_axis(source, false, point); + point = adjustAxis(source, false, point); } // Transform source points to long/lat, if they aren't already. if (source.projName === 'longlat') { @@ -95,7 +95,7 @@ export default function transform(source, dest, point, enforceAxis) { // DGR, 2010/11/12 if (enforceAxis && dest.axis !== 'enu') { - return adjust_axis(dest, true, point); + return adjustAxis(dest, true, point); } if (point && !hasZ) { @@ -104,7 +104,7 @@ export default function transform(source, dest, point, enforceAxis) { return point; } -export function adjust_axis(crs, denorm, point) { +export function adjustAxis(crs, denorm, point) { var xin = point.x, yin = point.y, zin = point.z || 0.0; diff --git a/src/proj4/transformer.ts b/src/proj4/transformer.ts new file mode 100644 index 00000000..efc44b6b --- /dev/null +++ b/src/proj4/transformer.ts @@ -0,0 +1,71 @@ +import { ALL_PROJECTIONS, DEFAULT_PROJECTIONS, ProjectionTransform } from './projections'; + +import type { ProjectionTransformDefinition } from './projections'; +import type { VectorPoint } from 's2-tools/geometry'; + +/** + * A Transformer class contains all projections necessary for converting coordinates from one + * projection to another. This is a modular class that can be extended to add new projections + * as needed to reduce code size and improve performance. + * Both forward and inverse projections are default set to wgs84. + */ +export class Transformer { + projections = new Map(); + source: ProjectionTransformDefinition; + destination: ProjectionTransformDefinition; + /** + * Prepares default projections + * @param sourceDef - can be a name or a coded definition + * @param destinationDef - can be a name or a coded definition + */ + constructor(sourceDef?: string, destinationDef?: string) { + for (const proj of DEFAULT_PROJECTIONS) this.insertProjection(proj); + const [wgs84] = DEFAULT_PROJECTIONS; + this.source = this.destination = wgs84; + // update source and destination if defined + if (sourceDef !== undefined) this.setsource(sourceDef); + if (destinationDef !== undefined) this.setDestination(destinationDef); + } + + /** @param sourceDef - can be a name or a coded definition */ + setsource(sourceDef: string) { + const projection: ProjectionTransformDefinition = + this.projections.get(sourceDef) ?? new ProjectionTransform(sourceDef); + this.source = projection; + } + + /** @param destinationDef - can be a name or a coded definition */ + setDestination(destinationDef: string) { + const projection: ProjectionTransformDefinition = + this.projections.get(destinationDef) ?? new ProjectionTransform(destinationDef); + this.destination = projection; + } + + /** @param proj - projection transform */ + insertProjection(proj: ProjectionTransformDefinition) { + for (const name of proj.names) this.projections.set(name, proj); + } + + /** + * @param p - vector point currently in the "source" projection + * @param _enforceAxis - enforce axis ensures axis consistency relative to the final projection + * @returns - vector point in the "destination" projection + */ + forward(p: VectorPoint, _enforceAxis?: boolean): VectorPoint { + return this.destination.forward(this.source.inverse(p)); + } + + /** + * @param p - vector point currently in the "destination" projection + * @param _enforceAxis - enforce axis ensures axis consistency relative to the final projection + * @returns - vector point in the "source" projection + */ + inverse(p: VectorPoint, _enforceAxis?: boolean): VectorPoint { + return this.source.forward(this.destination.inverse(p)); + } +} + +/** @param transformer - projection transformer */ +export function injectAllProjections(transformer: Transformer) { + for (const proj of ALL_PROJECTIONS) transformer.insertProjection(proj); +} diff --git a/src/proj4/util/match.ts b/src/proj4/util/match.ts new file mode 100644 index 00000000..420d8837 --- /dev/null +++ b/src/proj4/util/match.ts @@ -0,0 +1,21 @@ +/** + * @param obj - the object to search + * @param key - the key to search with + * @returns - the value of the key + */ +export default function match(obj: Record, key?: string): T | undefined { + if (key === undefined) return; + if (obj[key]) return obj[key]; + const keys = Object.keys(obj); + const ignoredChar = /[\s_\-/()]/g; + const lkey = key.toLowerCase().replace(ignoredChar, ''); + let i = -1; + let testkey, processedKey; + while (++i < keys.length) { + testkey = keys[i]; + processedKey = testkey.toLowerCase().replace(ignoredChar, ''); + if (processedKey === lkey) { + return obj[testkey]; + } + } +} diff --git a/src/readers/bufferReader.ts b/src/readers/bufferReader.ts index 743a7a70..b35ed7fc 100644 --- a/src/readers/bufferReader.ts +++ b/src/readers/bufferReader.ts @@ -2,6 +2,8 @@ import type { Reader } from '.'; /** A buffer reader is an extension of a DataView with some extra methods */ export class BufferReader extends DataView implements Reader { + textDecoder = new TextDecoder('utf-8'); + /** * @param buffer - the input buffer * @param byteOffset - offset in the buffer @@ -27,4 +29,21 @@ export class BufferReader extends DataView implements Reader { this.buffer.slice(this.byteOffset + begin, this.byteOffset + (end ?? this.byteLength)), ); } + + /** @param encoding - update the text decoder's encoding */ + setStringEncoding(encoding: string) { + this.textDecoder = new TextDecoder(encoding); + } + + /** + * @param byteOffset - Start of the string + * @param byteLength - Length of the string + * @returns - The string + */ + parseString(byteOffset: number, byteLength: number): string { + const { textDecoder } = this; + const data = this.slice(byteOffset, byteOffset + byteLength).buffer; + const out = textDecoder.decode(data, { stream: true }) + textDecoder.decode(); + return out.replace(/\0/g, '').trim(); + } } diff --git a/src/readers/fileReader.ts b/src/readers/fileReader.ts index 69329b38..5d92f47a 100644 --- a/src/readers/fileReader.ts +++ b/src/readers/fileReader.ts @@ -1,4 +1,4 @@ -import { closeSync, openSync, readSync, statSync } from 'fs'; // for random access +import { closeSync, openSync, readSync, statSync } from 'fs'; import type { Reader } from '.'; @@ -7,6 +7,7 @@ export default class FileReader implements Reader { #fileHandle: number; byteOffset: number = 0; byteLength: number; + textDecoder = new TextDecoder('utf-8'); /** * @param file - The path to the file @@ -142,6 +143,23 @@ export default class FileReader implements Reader { return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); } + /** @param encoding - update the text decoder's encoding */ + setStringEncoding(encoding: string) { + this.textDecoder = new TextDecoder(encoding); + } + + /** + * @param byteOffset - Start of the string + * @param byteLength - Length of the string + * @returns - The string + */ + parseString(byteOffset: number, byteLength: number): string { + const { textDecoder } = this; + const data = this.slice(byteOffset, byteOffset + byteLength).buffer; + const out = textDecoder.decode(data, { stream: true }) + textDecoder.decode(); + return out.replace(/\0/g, '').trim(); + } + /** * Closes the file */ diff --git a/src/readers/geojson/index.ts b/src/readers/geojson/index.ts deleted file mode 100644 index c5845549..00000000 --- a/src/readers/geojson/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Parser from './parser'; -import { fromFile } from 'gen-readlines'; - -/** - * @param filePath - * @param lineDelimited - */ -export default function geoJSONFileIterator( - filePath: string, - lineDelimited = false, -): Iterator { - let featureIterator: Iterator; - - if (lineDelimited) { - featureIterator = fromFile(filePath); - } else { - const parser = new Parser(); - featureIterator = parser.parse(filePath); - } - - return featureIterator; -} diff --git a/src/readers/geojson/parser.ts b/src/readers/geojson/parser.ts deleted file mode 100644 index d229ac33..00000000 --- a/src/readers/geojson/parser.ts +++ /dev/null @@ -1,181 +0,0 @@ -import fs from 'fs'; - -const LEFT_BRACE = 0x7b; -const RIGHT_BRACE = 0x7d; -const BACKSLASH = 0x5c; -const STRING = 0x22; - -/** - * - */ -export default class Parser { - chunkSize = 0; - buffer: Buffer = Buffer.alloc(665536); - offset = 0; - pos = 0; - fd = 0; - fileSize = 0; - braceDepth = 0; - feature: Buffer[] = []; - start: null | number = null; - end: null | number = null; - isObject = true; - - /** - * @param file - */ - parse(file: string): Iterator { - return _parse(this, file); - } - - // each line is a geojson object - /** - * - */ - nextValueFast(): void { - fs.readSync(this.fd, this.buffer, this.offset, this.chunkSize, this.offset); - } - - // since we know that a '{' is the start of a feature after we read a '"features"', - // than we start there to avoid reading in values that are not features. - // This is a modified Knuth–Morris–Pratt algorithm - /** - * - */ - setStartPosition(): boolean { - const features = Buffer.from('"features":'); - const featuresSize = features.length; - - let k = 0; - while (this.pos < this.chunkSize) { - if (features[k] === this.buffer[this.pos]) { - k++; - this.pos++; - if (k === featuresSize) { - return true; - } - } else { - k = 0; - this.pos++; - } - } - // if we made it here, we need to read in the next buffer chunk. - // If we hit the end of the file, return false - this.offset += this.chunkSize; - if (this.offset < this.fileSize) { - this.pos = 0; - if (this.offset + this.chunkSize < this.fileSize) { - this.chunkSize = this.fileSize - this.offset; - } - fs.readSync(this.fd, this.buffer, this.offset, this.chunkSize, this.offset); - return this.setStartPosition(); - } else { - return false; - } - } - - // everytime we see a "{" we start 'recording' the feature. If we see more "{" on our journey, we increment. - // Once we find the end of the feature, store the "start" and "end" indexes, slice the buffer and send out - // as a return. If we run out of buffer to read AKA we finish the file, we return a null. If we run - // out of the buffer, but we still have file left to read, just read into the buffer and continue on - /** - * - */ - nextValue(): null | Buffer { - // get started - while (this.pos < this.chunkSize) { - if (this.buffer[this.pos] === BACKSLASH) { - this.pos++; - } else if (this.buffer[this.pos] === STRING) { - if (!this.isObject) this.isObject = true; - else this.isObject = false; - } else if (this.buffer[this.pos] === LEFT_BRACE && this.isObject) { - if (this.braceDepth === 0) this.start = this.pos; - this.braceDepth++; // first brace is the start of the feature - } else if (this.buffer[this.pos] === RIGHT_BRACE && this.isObject) { - this.braceDepth--; // if this hits zero, we are at the end of the feature - if (this.braceDepth === 0) { - this.end = this.pos; - break; - } - } - this.pos++; - } - - // what if the last char in current buffer was a BACKSLASH? - // we need to make sure in the next buffer we account for increment - const incrementSpace = this.pos - this.chunkSize; - - if (this.start !== null && this.end !== null) { - this.pos++; - try { - this.feature.push(this.buffer.slice(this.start, this.end + 1)); - // this.feature = Buffer.concat(this.feature) - // this.feature = this.feature.toString() - // this.feature = this.feature.replace(/(\r\n|\n|\r)/gm, '') - // const feature = JSON.parse(this.feature) - const feature = Buffer.concat(this.feature); - // reset variables - this.feature = []; - this.start = null; - this.end = null; - this.braceDepth = 0; - this.isObject = true; - // return - return feature; - } catch (err) { - console.error(new Error('could not parse feature'), this.feature.toString()); - // reset variables - this.feature = []; - this.start = null; - this.end = null; - this.braceDepth = 0; - this.isObject = true; - return null; - } - } else { - // if offset isn't at filesize, increment buffer and start again - if (this.start !== null) { - this.feature.push(this.buffer.slice(this.start)); - this.start = 0; - } - this.offset += this.chunkSize; - if (this.offset < this.fileSize) { - this.pos = incrementSpace > 0 ? incrementSpace : 0; - if (this.offset + this.chunkSize > this.fileSize) { - this.chunkSize = this.fileSize - this.offset; - } - this.buffer = Buffer.alloc(this.chunkSize); - fs.readSync(this.fd, this.buffer, 0, this.chunkSize, this.offset); - return this.nextValue(); - } else { - return null; - } // end of file - } - } -} - -/** - * @param parser - * @param file - */ -function* _parse(parser: Parser, file: string): Iterator { - // setup - parser.fd = fs.openSync(file, 'r'); - const stats = fs.fstatSync(parser.fd); - parser.fileSize = stats.size; - parser.chunkSize = 665536; - // buffer the first chunk - fs.readSync(parser.fd, parser.buffer, parser.offset, parser.chunkSize, parser.offset); - // find out starting position - const set = parser.setStartPosition(); - if (!set) throw Error('File is not geojson'); - while (true) { - const feature = parser.nextValue(); - if (feature !== null) - yield feature; // may have removed the feature for some reason - else break; - } - // finish by closing the file - fs.closeSync(parser.fd); -} diff --git a/src/readers/index.ts b/src/readers/index.ts index cd57a8aa..9b75a687 100644 --- a/src/readers/index.ts +++ b/src/readers/index.ts @@ -18,4 +18,6 @@ export interface Reader { getUint8: (byteOffset: number) => number; // Methods slice: (begin: number, end: number) => DataView; + setStringEncoding: (encoding: string) => void; + parseString: (byteOffset: number, byteLength: number) => string; } diff --git a/src/readers/json/index.ts b/src/readers/json/index.ts new file mode 100644 index 00000000..b9b1d8c2 --- /dev/null +++ b/src/readers/json/index.ts @@ -0,0 +1,247 @@ +import type { Reader } from '..'; +import type { Features, JSONCollection } from 's2-tools/geometry'; + +/** Standard Buffer Reader for (Geo|S2)JSON */ +export class BufferJSONReader { + data: JSONCollection; + + /** @param data - the JSON data to parase */ + constructor(data: string | JSONCollection) { + if (typeof data === 'string') { + this.data = JSON.parse(data); + } else { + this.data = data; + } + } + + /** + * Generator to iterate over each (Geo|S2)JSON object in the file + * @yields {Features} + */ + *iterate(): Generator { + const { type } = this.data; + + if (type === 'FeatureCollection') { + for (const feature of this.data.features) { + yield feature; + } + } else if (type === 'Feature') { + yield this.data; + } else if (type === 'VectorFeature') { + yield this.data; + } else if (type === 'S2FeatureCollection') { + for (const feature of this.data.features) { + yield feature; + } + } else if (type === 'S2Feature') { + yield this.data; + } + } +} + +/** Parse (Geo|S2)JSON from a file that is in a newline-delimited format */ +export class NewLineDelimitedJSONReader { + /** @param reader - the reader to parse from */ + constructor(public reader: Reader) {} + + /** + * Generator to iterate over each (Geo|S2)JSON object in the file + * @yields {Features} + */ + *iterate(): Generator { + const { reader } = this; + let cursor = 0; + let offset = 0; + let partialLine = ''; + + while (offset < reader.byteLength) { + const length = Math.min(65_536, reader.byteLength - cursor); + // Prepend any partial line to the new chunk + const chunk = partialLine + reader.parseString(offset, length); + partialLine = ''; + // Split the chunk by newlines and yield each complete line + const lines = chunk.split('\n'); + for (let i = 0; i < lines.length - 1; i++) yield JSON.parse(lines[i]); + // Store the remaining partial line for the next iteration + partialLine = lines[lines.length - 1]; + // Update the cursor and offset + offset += length; + cursor += length; + } + + // Yield any remaining partial line after the loop + if (partialLine.length > 0) yield JSON.parse(partialLine); + } +} + +const LEFT_BRACE = 0x7b; +const RIGHT_BRACE = 0x7d; +const BACKSLASH = 0x5c; +const STRING = 0x22; + +/** A File Reader is designed to read millions of JSON objects if necessary. */ +export class JSONReader { + #chunkSize = 65_536; + #buffer: Uint8Array = new Uint8Array(); + #offset = 0; + #length: number; + #pos = 0; + #braceDepth = 0; + #feature: Uint8Array[] = []; + #start: null | number = null; + #end: null | number = null; + #isObject = true; + + /** + * @param reader - the reader to parse from + * @param chunkSize - the number of bytes to read at a time from the reader. [Default: 65_536] + */ + constructor( + public reader: Reader, + chunkSize?: number, + ) { + if (chunkSize) this.#chunkSize = chunkSize; + this.#length = reader.byteLength; + } + + /** + * Generator to iterate over each (Geo|S2)JSON object in the reader. + * @yields {Features} + */ + *iterate(): Generator { + if (this.#length <= this.#chunkSize) { + const reader = new BufferJSONReader(this.reader.parseString(0, this.#length)); + for (const feature of reader.iterate()) yield feature; + return; + } + // buffer the first chunk + this.#buffer = new Uint8Array(this.reader.slice(0, this.#chunkSize).buffer); + // find out starting position + const set = this.setStartPosition(); + if (!set) throw Error('File is not geojson or s2json'); + while (true) { + const feature = this.nextValue(); + if (feature !== undefined) yield feature; + else break; + } + } + + /** + * since we know that a '{' is the start of a feature after we read a '"features"', + * than we start there to avoid reading in values that are not features. + * This is a modified Knuth–Morris–Pratt algorithm + * @returns - true if the start position was found + */ + setStartPosition(): boolean { + const features = Buffer.from('"features":'); + const featuresSize = features.length; + + let k = 0; + while (this.#pos < this.#chunkSize) { + if (features[k] === this.#buffer[this.#pos]) { + k++; + this.#pos++; + if (k === featuresSize) { + return true; + } + } else { + k = 0; + this.#pos++; + } + } + // if we made it here, we need to read in the next buffer chunk. + // If we hit the end of the file, return false + this.#offset += this.#chunkSize; + if (this.#offset < this.#length) { + this.#pos = 0; + if (this.#offset + this.#chunkSize < this.#length) { + this.#chunkSize = this.#length - this.#offset; + } + this.#chunkSize = Math.min(65_536, this.#length - this.#offset); + this.#buffer = new Uint8Array( + this.reader.slice(this.#offset, this.#offset + this.#chunkSize).buffer, + ); + return this.setStartPosition(); + } else { + return false; + } + } + + /** + * everytime we see a "{" we start 'recording' the feature. If we see more "{" on our journey, we increment. + * Once we find the end of the feature, store the "start" and "end" indexes, slice the buffer and send out + * as a return. If we run out of buffer to read AKA we finish the file, we return a null. If we run + * out of the buffer, but we still have file left to read, just read into the buffer and continue on + * @returns - the feature or nothing if we hit the end of the file + */ + nextValue(): undefined | Features { + // get started + while (this.#pos < this.#chunkSize) { + if (this.#buffer[this.#pos] === BACKSLASH) { + this.#pos++; + } else if (this.#buffer[this.#pos] === STRING) { + if (!this.#isObject) this.#isObject = true; + else this.#isObject = false; + } else if (this.#buffer[this.#pos] === LEFT_BRACE && this.#isObject) { + if (this.#braceDepth === 0) this.#start = this.#pos; + this.#braceDepth++; // first brace is the start of the feature + } else if (this.#buffer[this.#pos] === RIGHT_BRACE && this.#isObject) { + this.#braceDepth--; // if this hits zero, we are at the end of the feature + if (this.#braceDepth === 0) { + this.#end = this.#pos; + break; + } + } + this.#pos++; + } + + // what if the last char in current buffer was a BACKSLASH? + // we need to make sure in the next buffer we account for increment + const incrementSpace = this.#pos - this.#chunkSize; + + if (this.#start !== null && this.#end !== null) { + this.#pos++; + try { + this.#feature.push(this.#buffer.subarray(this.#start, this.#end + 1)); + const feature = Buffer.concat(this.#feature); + // reset variables + this.#feature = []; + this.#start = null; + this.#end = null; + this.#braceDepth = 0; + this.#isObject = true; + // return + return JSON.parse(feature.toString('utf8')); + } catch (_err) { + console.error(new Error('could not parse feature'), this.#feature.toString()); + // reset variables + this.#feature = []; + this.#start = null; + this.#end = null; + this.#braceDepth = 0; + this.#isObject = true; + return; + } + } else { + // if offset isn't at filesize, increment buffer and start again + if (this.#start !== null) { + this.#feature.push(this.#buffer.subarray(this.#start)); + this.#start = 0; + } + this.#offset += this.#chunkSize; + if (this.#offset < this.#length) { + this.#pos = incrementSpace > 0 ? incrementSpace : 0; + if (this.#offset + this.#chunkSize > this.#length) { + this.#chunkSize = this.#length - this.#offset; + } + this.#chunkSize = Math.min(65_536, this.#length - this.#offset); + this.#buffer = new Uint8Array( + this.reader.slice(this.#offset, this.#offset + this.#chunkSize).buffer, + ); + return this.nextValue(); + } else { + return; + } // end of file + } + } +} diff --git a/src/readers/mmapReader.ts b/src/readers/mmapReader.ts new file mode 100644 index 00000000..a7b8d8bc --- /dev/null +++ b/src/readers/mmapReader.ts @@ -0,0 +1,161 @@ +import { mmap } from 'bun'; + +import type { Reader } from '.'; + +/** Reads data from a file */ +export default class MMapReader implements Reader { + #buffer: Uint8Array; + byteOffset: number = 0; + byteLength: number; + textDecoder = new TextDecoder('utf-8'); + + /** + * @param file - The path to the file + */ + constructor(file: string) { + this.#buffer = mmap(file); + this.byteOffset = this.#buffer.byteOffset; + this.byteLength = this.#buffer.byteLength; + } + + /** + * Reads a 64-bit unsigned integer (biguint64) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 64-bit unsigned integer as a bigint + */ + getBigInt64(byteOffset: number, littleEndian: boolean = false): bigint { + const slice = this.slice(byteOffset, byteOffset + 8); + return slice.getBigInt64(0, littleEndian); + } + + /** + * Reads a 64-bit unsigned integer (biguint64) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 64-bit unsigned integer as a bigint + */ + getBigUint64(byteOffset: number, littleEndian: boolean = false): bigint { + const slice = this.slice(byteOffset, byteOffset + 8); + return slice.getBigUint64(0, littleEndian); + } + + /** + * Reads a 32-bit floating-point number (float32) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 32-bit floating-point number as a number + */ + getFloat32(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 4); + return slice.getFloat32(0, littleEndian); + } + + /** + * Reads a 64-bit floating-point number (float64) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 64-bit floating-point number as a number + */ + getFloat64(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 8); + return slice.getFloat64(0, littleEndian); + } + + /** + * Reads a signed 16-bit integer (int16) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 16-bit signed integer value as a number + */ + getInt16(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 2); + return slice.getInt16(0, littleEndian); + } + + /** + * Reads a signed 32-bit integer (int32) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 32-bit signed integer value as a number + */ + getInt32(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 4); + return slice.getInt32(0, littleEndian); + } + + /** + * Reads a signed byte (int8) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @returns The byte value as a signed number + */ + getInt8(byteOffset: number): number { + const slice = this.slice(byteOffset, byteOffset + 1); + return slice.getInt8(0); + } + + /** + * Reads an unsigned 16-bit integer (uint16) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 16-bit unsigned integer value as a number + */ + getUint16(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 2); + return slice.getUint16(0, littleEndian); + } + + /** + * Reads an unsigned 32-bit integer (uint32) at the given byteOffset + * @param byteOffset - The position in the file to read from + * @param littleEndian - Optional, specifies if the value is stored in little-endian format. Defaults to false (big-endian). + * @returns The 32-bit unsigned integer value as a number + */ + getUint32(byteOffset: number, littleEndian: boolean = false): number { + const slice = this.slice(byteOffset, byteOffset + 4); + return slice.getUint32(0, littleEndian); + } + + /** + * Reads a single byte at the given byteOffset + * @param byteOffset - The position in the file to read from + * @returns The byte value as a number + */ + getUint8(byteOffset: number): number { + const slice = this.slice(byteOffset, byteOffset + 1); + return slice.getUint8(0); + } + + /** + * Get a slice of the file data as DataView + * @param begin - Beginning of the slice + * @param end - End of the slice. If not provided, the end of the data is used + * @returns - The data as a DataView + */ + slice(begin: number, end: number): DataView { + if (begin < 0 || end > this.byteLength || begin >= end) { + throw new RangeError('Invalid slice range'); + } + const sliceLength = end - begin; + const slice = this.#buffer.slice(begin, begin + sliceLength); + + return new DataView(slice.buffer, slice.byteOffset, slice.byteLength); + } + + /** @param encoding - update the text decoder's encoding */ + setStringEncoding(encoding: string) { + this.textDecoder = new TextDecoder(encoding); + } + + /** + * @param byteOffset - Start of the string + * @param byteLength - Length of the string + * @returns - The string + */ + parseString(byteOffset: number, byteLength: number): string { + const { textDecoder } = this; + const data = this.slice(byteOffset, byteOffset + byteLength).buffer; + const out = textDecoder.decode(data, { stream: true }) + textDecoder.decode(); + return out.replace(/\0/g, '').trim(); + } +} diff --git a/src/readers/nadgrid.ts b/src/readers/nadgrid.ts new file mode 100644 index 00000000..f01f7f50 --- /dev/null +++ b/src/readers/nadgrid.ts @@ -0,0 +1,238 @@ +import type { Reader } from '.'; + +import type { FeatureCollection, VectorFeature, VectorPoint } from 's2-tools/geometry'; + +/** + * Resources for details of NTv2 file formats: + * - https://web.archive.org/web/20140127204822if_/http://www.mgs.gov.on.ca:80/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf + * - http://mimaka.com/help/gs/html/004_NTV2%20Data%20Format.htm + */ + +/** Store Grids from a NTv2 file (.gsb) */ +export class NadGridStore { + grids = new Map(); + + /** @param grid - a nadgrid class to store */ + addGrid(grid: NadGrid) { + this.grids.set(grid.key, grid); + } + + /** + * @param key - the key or name of the grid + * @returns - the grid + */ + getGrid(key: string): NadGrid | undefined { + return this.grids.get(key); + } + + /** + * @param key - the key or name of the grid + * @param reader - the input data to parse + */ + addGridFromReader(key: string, reader: Reader) { + const grid = new NadGrid(key, reader); + this.addGrid(grid); + } +} + +/** The header of a NTv2 file */ +export interface NadGridHeader { + nFields: number; + nSubgridFields: number; + nSubgrids: number; + shiftType: string; + fromSemiMajorAxis: number; + fromSemiMinorAxis: number; + toSemiMajorAxis: number; + toSemiMinorAxis: number; +} + +/** Each subgrid has it's own data on how to decode the points inside the subgrid */ +export interface NadSubGridHeader { + name: string; + parent: string; + lowerLatitude: number; + upperLatitude: number; + lowerLongitude: number; + upperLongitude: number; + latitudeInterval: number; + longitudeInterval: number; + gridNodeCount: number; +} + +/** A single Node describing how to decode the point */ +export interface GridNode { + latitudeShift: number; + longitudeShift: number; + latitudeAccuracy: number; + longitudeAccuracy: number; +} + +/** The metadata inside each vector feature */ +export interface NadGridMetadata { + lowerLonLat: VectorPoint; + lonLatInterval: VectorPoint; + lonLatColumnCount: VectorPoint; + count: number; +} + +/** Load a binary NTv2 file (.gsb) */ +export class NadGrid { + isLittleEndian = false; + #header!: NadGridHeader; + features: VectorFeature[] = []; + + /** + * @param key - the key or name of the grid + * @param reader - the input data to parse + */ + constructor( + public key: string, + public reader: Reader, + ) { + this.#detectLittleEndian(); + this.#readHeader(); + this.#readSubGrids(); + } + + /** @returns - The header describing how to decode the feature */ + getHeader(): NadGridHeader { + return { ...this.#header }; + } + + /** + * Return all the features in the shapefile + * @returns - a collection of VectorFeatures + */ + getFeatureCollection(): FeatureCollection { + const { features } = this; + return { type: 'FeatureCollection', features }; + } + + /** + * Iterate over all features in the shapefile + * @yields {VectorFeature} + */ + *iterate(): IterableIterator> { + for (const feature of this.features) yield feature; + } + + /** + * @returns - true if the file is little-endian + */ + #detectLittleEndian(): boolean { + const { reader } = this; + let nFields = reader.getInt32(8, false); + if (nFields === 11) return false; + nFields = reader.getInt32(8, true); + if (nFields !== 11) { + console.warn('Failed to detect nadgrid endian-ness, defaulting to little-endian'); + } + return true; + } + + /** Parse the main header data. Describes the subgrids to decode lon-lat */ + #readHeader() { + const { reader, isLittleEndian } = this; + this.#header = { + nFields: reader.getInt32(8, isLittleEndian), + nSubgridFields: reader.getInt32(24, isLittleEndian), + nSubgrids: reader.getInt32(40, isLittleEndian), + shiftType: reader.parseString(56, 8), + fromSemiMajorAxis: reader.getFloat64(120, isLittleEndian), + fromSemiMinorAxis: reader.getFloat64(136, isLittleEndian), + toSemiMajorAxis: reader.getFloat64(152, isLittleEndian), + toSemiMinorAxis: reader.getFloat64(168, isLittleEndian), + }; + } + + /** Build all grid data */ + #readSubGrids() { + let gridOffset = 176; + for (let i = 0; i < this.#header.nSubgrids; i++) { + const subHeader = this.#readSubGridHeader(gridOffset); + const nodes = this.#readGridNodes(gridOffset, subHeader); + const lonColumnCount = Math.round( + 1 + (subHeader.upperLongitude - subHeader.lowerLongitude) / subHeader.longitudeInterval, + ); + const latColumnCount = Math.round( + 1 + (subHeader.upperLatitude - subHeader.lowerLatitude) / subHeader.latitudeInterval, + ); + + const feature: VectorFeature = { + type: 'VectorFeature', + properties: {}, + geometry: { + type: 'MultiPoint', + is3D: false, + coordinates: nodes.map(({ longitudeShift, latitudeShift }) => { + return { x: secondsToDegrees(longitudeShift), y: secondsToDegrees(latitudeShift) }; + }), + }, + metadata: { + lowerLonLat: { + x: secondsToDegrees(subHeader.lowerLongitude), + y: secondsToDegrees(subHeader.lowerLatitude), + }, + lonLatInterval: { x: subHeader.longitudeInterval, y: subHeader.latitudeInterval }, + lonLatColumnCount: { x: lonColumnCount, y: latColumnCount }, + count: subHeader.gridNodeCount, + }, + }; + this.features.push(feature); + gridOffset += 176 + subHeader.gridNodeCount * 16; + } + } + + /** + * @param offset - offset to read in the subgrid header + * @returns - the subgrid header + */ + #readSubGridHeader(offset: number): NadSubGridHeader { + const { reader, isLittleEndian } = this; + return { + name: reader.parseString(offset + 8, 8), + parent: reader.parseString(offset + 24, 8), + lowerLatitude: reader.getFloat64(offset + 72, isLittleEndian), + upperLatitude: reader.getFloat64(offset + 88, isLittleEndian), + lowerLongitude: reader.getFloat64(offset + 104, isLittleEndian), + upperLongitude: reader.getFloat64(offset + 120, isLittleEndian), + latitudeInterval: reader.getFloat64(offset + 136, isLittleEndian), + longitudeInterval: reader.getFloat64(offset + 152, isLittleEndian), + gridNodeCount: reader.getInt32(offset + 168, isLittleEndian), + }; + } + + /** + * @param offset - offset of the grid + * @param gridHeader - header of the grid + * @returns - an array of grid nodes + */ + #readGridNodes(offset: number, gridHeader: NadSubGridHeader): GridNode[] { + const { reader, isLittleEndian } = this; + const nodesOffset = offset + 176; + const gridRecordLength = 16; + const gridShiftRecords = []; + for (let i = 0; i < gridHeader.gridNodeCount; i++) { + const record = { + latitudeShift: reader.getFloat32(nodesOffset + i * gridRecordLength, isLittleEndian), + longitudeShift: reader.getFloat32(nodesOffset + i * gridRecordLength + 4, isLittleEndian), + latitudeAccuracy: reader.getFloat32(nodesOffset + i * gridRecordLength + 8, isLittleEndian), + longitudeAccuracy: reader.getFloat32( + nodesOffset + i * gridRecordLength + 12, + isLittleEndian, + ), + }; + gridShiftRecords.push(record); + } + return gridShiftRecords; + } +} + +/** + * @param seconds - number of seconds + * @returns radians + */ +function secondsToDegrees(seconds: number): number { + return seconds / 3600; +} diff --git a/src/readers/osm/areaBuilder.zig b/src/readers/osm/areaBuilder.zig deleted file mode 100644 index 2fa34c2d..00000000 --- a/src/readers/osm/areaBuilder.zig +++ /dev/null @@ -1,205 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const ArrayList = std.ArrayList; -const json = @import("json"); -const Feature = json.Feature; -const Point = json.Point; -const Polygon = json.Polygon; -const LineString = json.LineString; -const MultiPolygon = json.MultiPolygon; -const relation = @import("relation.zig"); -const Member = relation.Member; -const RoleType = relation.RoleType; -const WayMember = @import("way.zig").WayMember; - -/// Given a group of Members whose type is "way", -/// build a polygon or multipolygon Feature. -pub fn buildArea(members: []*Member, id: ?u64, properties: json.Value, arena: *std.heap.ArenaAllocator) !?Feature { - // prep variables - const allocator = arena.allocator(); - const polygons = ArrayList(Polygon).init(allocator); - const currentPolygon = ArrayList(LineString).init(allocator); - const currentRing = ArrayList(Point).init(allocator); - - // prepare step: members are stored out of order - sortMembers(members); - - for (members) |member| { - // Using "isClockwise", depending on whether the ring is outer or inner, - // we may need to reverse the order of the points. Every time we find the - // first and last point are the same, close out the ring, add it to the current - // polygon, and start a new ring. if the current polygon is NOT empty, we store - // it in the polygons list and start a new one before adding the completed ring. - // NOTE: Due to the nature of OSM data, it is possible that resulting ring is reversed. - // Check against the current ring to see if the way needs to be edited. - // - // grab the geometry from the member - var geometry = try member.item.way.nodes(allocator); - if (geometry == null) return null; - // store in current ring, checking current rings order - if (currentRing.items.len == 0) { - try currentRing.appendSlice(geometry.?); - } else { - try currentRing.appendSlice(geometry.?[1..]); - } - // if current rings first and last point are the same, close out the ring - if (equalPoints(currentRing.items[0], currentRing.items[currentRing.items.len - 1])) { - // if ring is outer and clockwise, reverse the order of the points - // also if the ring is an inner and counter-clockwise, reverse the order of the points - if (member.role == RoleType.Outer and isClockwise(currentRing.items) or - member.role == RoleType.Inner and !isClockwise(currentRing.items)) - { - std.mem.reverse(Point, currentRing.items); - } - // add the ring to the current polygon. If member role is outer and - // currentPolygon already has data, we need to store the current poly and - // start a new polygon. - // If the member role is inner, we can add the ring to the - // current polygon. - if (member.role == RoleType.Outer and currentPolygon.items.len > 0) { - try polygons.append(try currentPolygon.toOwnedSlice()); - } - try currentPolygon.append(try currentRing.toOwnedSlice()); - } - } - - // Last step is to build: - // flush the current polygon if it exists - if (currentPolygon.items.len > 0) { - try polygons.append(try currentPolygon.toOwnedSlice()); - } - // grab the polys and return a feature - const polys = try polygons.toOwnedSlice(); - if (polys.len == 1) { - return Feature{ - .id = id, - .geometry = .{ .polygon = polys[0] }, - .properties = properties, - }; - } else { - return Feature{ - .id = id, - .geometry = .{ .multiPolygon = polys }, - .properties = properties, - }; - } -} - -fn equalPoints(a: []f64, b: []f64) bool { - return a[0] == b[0] and a[1] == b[1]; -} - -/// If the returned sum < 0, the ring is counter-clockwise. -/// If the returned sum > 0, the ring is clockwise. -fn isClockwise(ring: LineString) bool { - var sum: f64 = 0; - if (ring.len < 4) return false; - var i: usize = 1; - var prev: Point = ring[0]; - var curr: Point = undefined; - while (i < ring.len) : (i += 1) { - curr = ring[i]; - sum += (curr[0] - prev[0]) * (curr[1] + prev[1]); - prev = curr; - } - return sum > 0; -} - -/// osm throws relation members out of order, so we need to not only sort them -/// but also check if the first and last points of each way follow the same direction. -fn sortMembers(members: []*Member) void { - if (members.len < 3) return; - var i: usize = 0; - while (i < members.len - 1) : (i += 1) { - const curWay = members[i].*.item.way; - const curFirstPoint = curWay._refs[0]; - const curLastPoint = curWay._refs[curWay._refs.len - 1]; - // if current way is already self closing break - if (curFirstPoint == curLastPoint) break; - var j = i + 1; - while (j < members.len) : (j += 1) { - const nextWay = members[j].*.item.way; - const nextFirstPoint = nextWay._refs[0]; - const nextLastPoint = nextWay._refs[nextWay._refs.len - 1]; - // if we find a match between any of the points, swap the member positions - // if curFirstPoint == nextFirstPoint or curLastPoint == nextLastPoint - // swap the _refs order - const equalFirst = curFirstPoint == nextFirstPoint; - const equalLast = curLastPoint == nextLastPoint; - const equalFirstLast = curFirstPoint == nextLastPoint; - const equalLastFirst = curLastPoint == nextFirstPoint; - if (equalFirst or equalLast or equalFirstLast or equalLastFirst) { - if (equalFirst) { - std.mem.reverse(i64, curWay._refs); - } else if (equalLast) { - std.mem.reverse(i64, nextWay._refs); - } else if (equalFirstLast) { - std.mem.reverse(i64, curWay._refs); - std.mem.reverse(i64, nextWay._refs); - } - // we want to move the found member to be next to the current member - if (i + 1 != j) std.mem.swap(Member, members[i + 1], members[j]); - break; - } - } - } -} - -test "sortMethods" { - const ref1 = [_]i64{ 1, 2, 3 }; - const way1 = WayMember{ .primitiveBlock = undefined, ._refs = &ref1 }; - const m1 = Member{ .role = RoleType.Outer, .item = .{ .way = way1 } }; - const ref2 = [_]i64{ 3, 4, 5 }; - const way2 = WayMember{ .primitiveBlock = undefined, ._refs = &ref2 }; - const m2 = Member{ .role = RoleType.Outer, .item = .{ .way = way2 } }; - const ref3 = [_]i64{ 5, 6, 7 }; - const way3 = WayMember{ .primitiveBlock = undefined, ._refs = &ref3 }; - const m3 = Member{ .role = RoleType.Outer, .item = .{ .way = way3 } }; - const ref4 = [_]i64{ 1, 8, 7 }; - const way4 = WayMember{ .primitiveBlock = undefined, ._refs = &ref4 }; - const m4 = Member{ .role = RoleType.Outer, .item = .{ .way = way4 } }; - // add inner - const ref5 = [_]i64{ 11, 22, 33 }; - const way5 = WayMember{ .primitiveBlock = undefined, ._refs = &ref5 }; - const m5 = Member{ .role = RoleType.Inner, .item = .{ .way = way5 } }; - const ref6 = [_]i64{ 55, 44, 33 }; - const way6 = WayMember{ .primitiveBlock = undefined, ._refs = &ref6 }; - const m6 = Member{ .role = RoleType.Inner, .item = .{ .way = way6 } }; - const ref7 = [_]i64{ 55, 66, 77 }; - const way7 = WayMember{ .primitiveBlock = undefined, ._refs = &ref7 }; - const m7 = Member{ .role = RoleType.Inner, .item = .{ .way = way7 } }; - const ref8 = [_]i64{ 77, 88, 11 }; - const way8 = WayMember{ .primitiveBlock = undefined, ._refs = &ref8 }; - const m8 = Member{ .role = RoleType.Inner, .item = .{ .way = way8 } }; - - const members = [_]*Member{ &m1, &m3, &m4, &m2, &m8, &m6, &m7, &m5 }; - sortMembers(&members); - // outer (tests equalFirst and equalLast) - try testing.expectEqualSlices(i64, &[_]i64{ 3, 2, 1 }, members[0].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 1, 8, 7 }, members[1].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 7, 6, 5 }, members[2].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 5, 4, 3 }, members[3].*.item.way._refs); - // inner (tests equalFirstLast) - try testing.expectEqualSlices(i64, &[_]i64{ 11, 88, 77 }, members[4].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 77, 66, 55 }, members[5].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 55, 44, 33 }, members[6].*.item.way._refs); - try testing.expectEqualSlices(i64, &[_]i64{ 33, 22, 11 }, members[7].*.item.way._refs); -} - -test "signedArea outer (ccw) is negative" { - const p1 = [_]f64{ 0, 0 }; - const p2 = [_]f64{ 1, 0 }; - const p3 = [_]f64{ 1, 1 }; - const p4 = [_]f64{ 0, 1 }; - const ls = [_][]f64{ &p1, &p2, &p3, &p4, &p1 }; - try testing.expect(!isClockwise(&ls)); -} - -test "signedArea inner (cw) is positive" { - const p1 = [_]f64{ 0, 0 }; - const p2 = [_]f64{ 0, 1 }; - const p3 = [_]f64{ 1, 1 }; - const p4 = [_]f64{ 1, 0 }; - const ls = [_][]f64{ &p1, &p2, &p3, &p4, &p1 }; - try testing.expect(isClockwise(&ls)); -} diff --git a/src/readers/osm/blob.ts b/src/readers/osm/blob.ts new file mode 100644 index 00000000..cc989466 --- /dev/null +++ b/src/readers/osm/blob.ts @@ -0,0 +1,77 @@ +import { decompressStream } from 's2-tools/util'; + +import type { Pbf as Protobuf } from 'open-vector-tile'; + +export const MAX_HEADER_SIZE = 64 * 1024; + +// blobs have a max size of 32MB +export const MAX_BLOB_SIZE = 32 * 1024 * 1024; + +/** + * A file contains an sequence of fileblock headers, each prefixed by + * their length in network byte order, followed by a data block + * containing the actual data. Types starting with a "_" are reserved. + * example: { type: 'OSMHeader', indexdata: null, datasize: 173 } + */ +export class BlobHeader { + type: string = ''; + indexdata: Uint8Array = new Uint8Array(0); + datasize = 0; + + /** + * @param pbf + */ + constructor(pbf: Protobuf) { + pbf.readFields(this.#readLayer, this, 0); + } + + /** + * @param tag + * @param blobHeader + * @param pbf + */ + #readLayer(tag: number, blobHeader: BlobHeader, pbf: Protobuf): void { + if (tag == 1) blobHeader.type = pbf.readString(); + else if (tag == 2) blobHeader.indexdata = pbf.readBytes(); + else if (tag == 3) blobHeader.datasize = pbf.readVarint(); + else throw new Error('unknown tag ' + tag); + } +} + +/// STORAGE LAYER: Storing primitives. +/** + * + */ +export class Blob { + raw_size = 0; + data: Uint8Array | Promise = new Uint8Array(0); + + /** + * @param pbf + */ + constructor(pbf: Protobuf) { + pbf.readFields(this.#readLayer, this, 0); + } + + /** + * @param tag + * @param blob + * @param pbf + */ + #readLayer(tag: number, blob: Blob, pbf: Protobuf): void { + // no compression + if (tag == 1) blob.data = pbf.readBytes(); + else if (tag == 2) blob.raw_size = pbf.readVarint(); + // ZLIB: + else if (tag == 3) blob.data = decompressStream(pbf.readBytes()); + // For LZMA compressed data (optional) + else if (tag == 4) blob.data = pbf.readBytes(); + // Formerly used for bzip2 compressed data. Deprecated in 2010. + else if (tag == 5) throw new Error('bzip2 not supported'); + // For LZ4 compressed data (optional) + else if (tag == 6) blob.data = pbf.readBytes(); + // For ZSTD compressed data (optional) + else if (tag == 7) blob.data = pbf.readBytes(); + else throw new Error('unknown tag ' + tag); + } +} diff --git a/src/readers/osm/fileFormat.zig b/src/readers/osm/fileFormat.zig deleted file mode 100644 index eb641b29..00000000 --- a/src/readers/osm/fileFormat.zig +++ /dev/null @@ -1,103 +0,0 @@ -const std = @import("std"); -const io = std.io; -const zlib = std.compress.zlib; -const lzma = std.compress.lzma; -const zstd = std.compress.zstd; -const Protobuf = @import("pbf").Protobuf; -const ArrayList = std.ArrayList; - -pub const MAX_HEADER_SIZE = 64 * 1024; - -// blobs have a max size of 32MB -pub const MAX_BLOB_SIZE = 32 * 1024 * 1024; - -const BlobError = error{ - Deprecated, - Unsupported, -}; - -const DecodeType = enum { - Zlib, - Lzma, - Zstd, -}; - -/// A file contains an sequence of fileblock headers, each prefixed by -/// their length in network byte order, followed by a data block -/// containing the actual data. Types starting with a "_" are reserved. -/// example: { type: 'OSMHeader', indexdata: null, datasize: 173 } -pub const BlobHeader = struct { - type: []const u8 = undefined, - indexdata: ?[]u8 = null, - datasize: i32 = undefined, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !BlobHeader { - var blobHeader = BlobHeader{}; - try pbf.readFields(BlobHeader, &blobHeader, BlobHeader.readLayer); - return blobHeader; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.type = pbf.readString(), - 2 => self.indexdata = pbf.readBytes(), - 3 => self.datasize = pbf.readVarint(i32), - else => unreachable, - } - } -}; - -/// STORAGE LAYER: Storing primitives. -pub const Blob = struct { - raw_size: ?i32 = null, - data: []u8 = undefined, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !Blob { - var blob = Blob{}; - try pbf.readFields(Blob, &blob, Blob.readLayer); - return blob; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - // No compression - 1 => self.data = pbf.readBytes(), - 2 => self.raw_size = pbf.readVarint(i32), - // ZLIB: - 3 => self.data = try decompress(pbf.readBytes(), DecodeType.Zlib, pbf.arena.allocator()), - // For LZMA compressed data (optional) - 4 => self.data = try decompress(pbf.readBytes(), DecodeType.Lzma, pbf.arena.allocator()), - // Formerly used for bzip2 compressed data. Deprecated in 2010. - 5 => return BlobError.Deprecated, - // NOTE: since Zig currently doesn't have an LZ4 implementation, we can't support this - // For LZ4 compressed data (optional) - 6 => return BlobError.Unsupported, - // For ZSTD compressed data (optional) - 7 => self.data = try decompress(pbf.readBytes(), DecodeType.Zstd, pbf.arena.allocator()), - else => unreachable, - } - } -}; - -fn decompress(data: []u8, decodeType: DecodeType, allocator: std.mem.Allocator) ![]u8 { - var inStream = io.fixedBufferStream(data); - switch (decodeType) { - .Zlib => { - var decompressStream = try zlib.zlibStream(allocator, inStream.reader()); - defer decompressStream.deinit(); - return try decompressStream.reader().readAllAlloc(allocator, MAX_BLOB_SIZE); - }, - .Lzma => { - var decompressStream = try lzma.decompress(allocator, inStream.reader()); - defer decompressStream.deinit(); - return try decompressStream.reader().readAllAlloc(allocator, MAX_BLOB_SIZE); - }, - .Zstd => { - var decompressStream = zstd.decompressStream(allocator, inStream.reader()); - defer decompressStream.deinit(); - return try decompressStream.reader().readAllAlloc(allocator, MAX_BLOB_SIZE); - }, - } -} diff --git a/src/readers/osm/headerBlock.ts b/src/readers/osm/headerBlock.ts new file mode 100644 index 00000000..ae15f51a --- /dev/null +++ b/src/readers/osm/headerBlock.ts @@ -0,0 +1,125 @@ +import type { BBox } from 's2-tools/geometry'; +import type { Pbf as Protobuf } from 'open-vector-tile'; + +/** + * + */ +export interface OSMHeader { + bbox: BBox; + required_features: string[]; + optional_features: string[]; + writingprogram?: string; + source?: string; + // Tags that allow continuing an Osmosis replication + // Replication timestamp, expressed in seconds since the epoch, + // otherwise the same value as in the "timestamp=..." field + // in the state.txt file used by Osmosis. + osmosis_replication_timestamp: number; + // Replication sequence number (sequenceNumber in state.txt). + osmosis_replication_sequence_number: number; + // Replication base URL (from Osmosis' configuration.txt file). + osmosis_replication_base_url?: string; +} + +/** + * + */ +export class HeaderBlock { + bbox = new HeaderBBox(); + // Additional tags to aid in parsing this dataset + required_features: string[] = []; + optional_features: string[] = []; + writingprogram?: string; + source?: string; + // Tags that allow continuing an Osmosis replication + // Replication timestamp, expressed in seconds since the epoch, + // otherwise the same value as in the "timestamp=..." field + // in the state.txt file used by Osmosis. + osmosis_replication_timestamp = -1; + // Replication sequence number (sequenceNumber in state.txt). + osmosis_replication_sequence_number = -1; + // Replication base URL (from Osmosis' configuration.txt file). + osmosis_replication_base_url?: string; + + /** + * @param pbf + */ + constructor(pbf: Protobuf) { + pbf.readMessage(this.readLayer, this); + } + + /** + * + */ + toHeader(): OSMHeader { + const { + required_features, + optional_features, + writingprogram, + source, + osmosis_replication_timestamp, + osmosis_replication_sequence_number, + osmosis_replication_base_url, + } = this; + + return { + bbox: this.bbox.toBBox(), + required_features, + optional_features, + writingprogram, + source, + osmosis_replication_timestamp, + osmosis_replication_sequence_number, + osmosis_replication_base_url, + }; + } + + /** + * @param tag + * @param header + * @param pbf + */ + readLayer(tag: number, header: HeaderBlock, pbf: Protobuf): void { + if (tag == 1) header.bbox = pbf.readMessage(header.bbox.readLayer, header.bbox); + else if (tag == 4) header.required_features.push(pbf.readString()); + else if (tag == 5) header.optional_features.push(pbf.readString()); + else if (tag == 16) header.writingprogram = pbf.readString(); + else if (tag == 17) header.source = pbf.readString(); + else if (tag == 32) header.osmosis_replication_timestamp = pbf.readVarint(); + else if (tag == 33) header.osmosis_replication_sequence_number = pbf.readVarint(); + else if (tag == 34) header.osmosis_replication_base_url = pbf.readString(); + else throw new Error('unknown tag ' + tag); + } +} + +/** + * The bounding box field in the OSM header. BBOX, as used in the OSM + * header. Units are always in nanodegrees -- they do not obey + * granularity rules. + */ +export class HeaderBBox { + left = -1; + right = -1; + top = -1; + bottom = -1; + + /** + * @param tag + * @param bbox + * @param pbf + */ + readLayer(tag: number, bbox: HeaderBBox, pbf: Protobuf): void { + if (tag == 1) bbox.left = pbf.readVarint(); + else if (tag == 2) bbox.right = pbf.readVarint(); + else if (tag == 3) bbox.top = pbf.readVarint(); + else if (tag == 4) bbox.bottom = pbf.readVarint(); + else throw new Error('unknown tag ' + tag); + } + + /** + * + */ + toBBox(): [left: number, bottom: number, right: number, top: number] { + return [this.left, this.bottom, this.right, this.top]; + } +} diff --git a/src/readers/osm/headerBlock.zig b/src/readers/osm/headerBlock.zig deleted file mode 100644 index 03e3db62..00000000 --- a/src/readers/osm/headerBlock.zig +++ /dev/null @@ -1,74 +0,0 @@ -const std = @import("std"); -const ArrayList = std.ArrayList; -const Protobuf = @import("pbf").Protobuf; -const Info = @import("info.zig").Info; - -pub const HeaderBlock = struct { - bbox: ?HeaderBBox = null, - // Additional tags to aid in parsing this dataset - required_features: ArrayList([]const u8), - optional_features: ArrayList([]const u8), - writingprogram: ?[]const u8 = null, - source: ?[]const u8 = null, - // Tags that allow continuing an Osmosis replication - // Replication timestamp, expressed in seconds since the epoch, - // otherwise the same value as in the "timestamp=..." field - // in the state.txt file used by Osmosis. - osmosis_replication_timestamp: i64 = -1, - // Replication sequence number (sequenceNumber in state.txt). - osmosis_replication_sequence_number: i64, - // Replication base URL (from Osmosis' configuration.txt file). - osmosis_replication_base_url: ?[]const u8 = null, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !HeaderBlock { - const allocator = pbf.arena.allocator(); - var headerBlock = HeaderBlock{ - .required_features = ArrayList([]const u8).init(allocator), - .optional_features = ArrayList([]const u8).init(allocator), - }; - try pbf.readMessage(HeaderBlock, &headerBlock, readLayer); - return headerBlock; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.bbox = try HeaderBBox.Init(pbf), - 4 => try self.required_features.append(pbf.readString()), - 5 => try self.optional_features.append(pbf.readString()), - 16 => self.writingprogram = try pbf.readString(), - 17 => self.source = try pbf.readString(), - 32 => self.osmosis_replication_timestamp = pbf.readVarint(i64), - 33 => self.osmosis_replication_sequence_number = pbf.readVarint(i64), - 34 => self.osmosis_replication_base_url = try pbf.readString(), - else => unreachable, - } - } -}; - -/// The bounding box field in the OSM header. BBOX, as used in the OSM -/// header. Units are always in nanodegrees -- they do not obey -/// granularity rules. -pub const HeaderBBox = struct { - left: i64 = -1, - right: i64 = -1, - top: i64 = -1, - bottom: i64 = -1, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !HeaderBBox { - var headerBBox = HeaderBBox{}; - try pbf.readMessage(HeaderBBox, &headerBBox, readLayer); - return headerBBox; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.left = pbf.readVarint(i64), - 2 => self.right = pbf.readVarint(i64), - 3 => self.top = pbf.readVarint(i64), - 4 => self.bottom = pbf.readVarint(i64), - else => unreachable, - } - } -}; diff --git a/src/readers/osm/index.ts b/src/readers/osm/index.ts new file mode 100644 index 00000000..fef7e39b --- /dev/null +++ b/src/readers/osm/index.ts @@ -0,0 +1,217 @@ +import { HeaderBlock } from './headerBlock'; +import { PrimitiveBlock } from './primitive'; +import { Pbf as Protobuf } from 'open-vector-tile'; +import { Blob, BlobHeader } from './blob'; + +import type { InfoBlock } from './info'; +import type { KVStore } from 's2-tools/db'; +import type { OSMHeader } from './headerBlock'; +import type { Reader } from '../index'; +import type { VectorFeature, VectorLineString, VectorPoint } from 's2-tools/geometry'; + +export type * from './blob'; +export type * from './headerBlock'; +export type * from './info'; +export type * from './node'; +export type * from './relation'; +export type * from './way'; + +// TODO: Add bbox for each node, way, relation toVectorFeature + +/** + * + */ +export type FilterType = 'All' | 'Node' | 'Way' | 'Relation'; + +/** + * + */ +export type FilterMap = Map; + +/** + * + */ +export class TagFilter { + #filters: FilterMap = new Map(); + #nodeFilters: FilterMap = new Map(); + #wayFilters: FilterMap = new Map(); + #relationFilters: FilterMap = new Map(); + + /** + * @param filterType + */ + #getFilter(filterType: FilterType): FilterMap { + if (filterType === 'All') return this.#filters; + else if (filterType === 'Node') return this.#nodeFilters; + else if (filterType === 'Way') return this.#wayFilters; + else return this.#relationFilters; + } + + /** + * @param filterType + * @param key + * @param value + */ + addFilter(filterType: FilterType, key: string, value?: string): void { + const filter: FilterMap = this.#getFilter(filterType); + filter.set(key, value); + } + + /** + * @param filterType + * @param key + * @param value + */ + matchFound(filterType: FilterType, key: string, value?: string): boolean { + const filter: FilterMap = this.#getFilter(filterType); + // check all filters first + if (this.#filters.has(key)) { + const filterValue = this.#filters.get(key); + if (filterValue === value) return true; + } + // check type-specific filters + if (filterType !== 'All' && filter.has(key)) { + const filterValue = filter.get(key); + if (filterValue === value) return true; + } + return false; + } +} + +/** OSM Reader options */ +export interface OsmReaderOptions { + /** if true, remove nodes that have no tags [Default = true] */ + removeEmptyNodes?: boolean; + /** If provided, filters of the */ + tagFilter?: TagFilter; + /** If set to true, nodes will be skipped. [Default = false] */ + skipNodes?: boolean; + /** If set to true, ways will be skipped. [Default = false] */ + skipWays?: boolean; + /** If set to true, relations will be skipped. [Default = false] */ + skipRelations?: boolean; + /** + * If set to true, ways will be converted to areas if they are closed. + * NOTE: They are upgraded anyways if the tag "area" is set to "yes". + * [Default = false] + */ + upgradeWaysToAreas?: boolean; +} + +/** + * + */ +export class OSMReader { + /** if true, remove nodes that have no tags [Default = true] */ + removeEmptyNodes: boolean; + /** If provided, filters of the */ + tagFilter?: TagFilter; + /** If set to true, nodes will be skipped */ + skipNodes: boolean; + /** If set to true, ways will be skipped */ + skipWays: boolean; + /** If set to true, relations will be skipped */ + skipRelations: boolean; + /** + * If set to true, ways will be converted to areas if they are closed. + * NOTE: They are upgraded anyways if the tag "area" is set to "yes". + * [Default = false] + */ + upgradeWaysToAreas: boolean; + + nodes: KVStore = new Map(); + ways: KVStore = new Map(); + #offset = 0; + + /** + * @param reader + * @param options + */ + constructor( + public reader: Reader, + public options?: OsmReaderOptions, + ) { + this.removeEmptyNodes = options?.removeEmptyNodes ?? true; + this.tagFilter = options?.tagFilter; + this.skipNodes = options?.skipNodes ?? false; + this.skipWays = options?.skipWays ?? false; + this.skipRelations = options?.skipRelations ?? false; + this.upgradeWaysToAreas = options?.upgradeWaysToAreas ?? false; + } + + /** + * + */ + async *iterate(): AsyncGenerator> { + this.#offset = 0; + // skip the header + await this.#next(); + + while (true) { + const blob = await this.#next(); + if (blob === undefined) break; + const pb = await this.#readBlob(blob); + for (const pg of pb.primitiveGroups) { + for (const node of pg.nodes) yield node.toVectorFeature(); + for (const way of pg.ways) { + const feature = way.toVectorFeature(); + if (feature !== undefined) yield feature; + } + for (const relation of pg.relations) { + const feature = relation.toVectorFeature(); + if (feature !== undefined) yield feature; + } + } + } + } + + /** + * + */ + async getHeader(): Promise { + this.#offset = 0; + const blobHeader = await this.#next(); + if (blobHeader === undefined) throw new Error('Header not found'); + const headerBlock = new HeaderBlock(new Protobuf(new Uint8Array(blobHeader.buffer))); + + return headerBlock.toHeader(); + } + + /** + * @param isHeader + * @param offset + */ + async #next(): Promise { + const { reader } = this; + // if we've already read all the data, return null + if (this.#offset >= reader.byteLength) return; + // STEP 1: Get blob size + // read length of current blob + const length = reader.getInt32(this.#offset); + this.#offset += 4; + const blobHeaderData = reader.slice(this.#offset, this.#offset + length); + this.#offset += length; + // build a blob header + const pbf = new Protobuf(new Uint8Array(blobHeaderData.buffer)); + const blobHeader = new BlobHeader(pbf); + // STEP 2: Get blob data + const compressedBlobData = reader.slice(this.#offset, this.#offset + blobHeader.datasize); + this.#offset += blobHeader.datasize; + + return compressedBlobData; + } + + /** + * @param blob + * @param data + */ + async #readBlob(data: DataView): Promise { + // Blob data is PBF encoded and ?compressed, so we need to parse & decompress it first + let pbf = new Protobuf(new Uint8Array(data.buffer)); + const blob = new Blob(pbf); + pbf = new Protobuf(await blob.data); + // Parse the PrimitiveBlock and read its contents. + // all nodes/ways/relations that can be filtered already are on invocation. + return new PrimitiveBlock(pbf, this); + } +} diff --git a/src/readers/osm/info.ts b/src/readers/osm/info.ts new file mode 100644 index 00000000..f3ada085 --- /dev/null +++ b/src/readers/osm/info.ts @@ -0,0 +1,199 @@ +import type { PrimitiveBlock } from './primitive'; +import type { Pbf as Protobuf } from 'open-vector-tile'; + +/** + * + */ +export interface InfoBlock { + version?: number; + timestamp?: number; + changeset?: number; + uid?: number; + user?: string; + visible?: boolean; +} + +/** Optional metadata that may be included into each primitive. */ +export class Info { + #version?: number; + #timestamp?: number; // (millisec_stamp = timestamp*dateGranularity.) + #changeset?: number; + #uid?: number; + #userSid?: number; // String IDs for usernames. + // The visible flag is used to store history information. It indicates that + // the current object version has been created by a delete operation on the + // OSM API. + // When a writer sets this flag, it MUST add a required_features tag with + // value "HistoricalInformation" to the HeaderBlock. + // If this flag is not available for some object it MUST be assumed to be + // true if the file has the required_features tag "HistoricalInformation" + // set. + #visible = true; + + /** + * @param primitiveBlock + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + pbf?: Protobuf, + ) { + if (pbf) pbf.readMessage(this.#readLayer, this); + } + + /** + * + */ + toBlock(): InfoBlock { + return { + version: this.#version, + timestamp: this.timeStamp(), + changeset: this.#changeset, + uid: this.#uid, + user: this.user(), + visible: this.#visible, + }; + } + + /** + * @param primitiveBlock + * @param version + * @param timestamp + * @param changeset + * @param uid + * @param userSid + * @param visible + */ + static fromDense( + primitiveBlock: PrimitiveBlock, + version: number, + timestamp: number, + changeset: number, + uid: number, + userSid: number, + visible?: boolean, + ): Info { + const info = new Info(primitiveBlock); + info.#version = version; + info.#timestamp = timestamp; + info.#changeset = changeset; + info.#uid = uid; + info.#userSid = userSid; + info.#visible = visible ?? true; + return info; + } + + /** + * + */ + timeStamp(): number | undefined { + if (this.#timestamp === undefined) return; + return this.#timestamp * this.primitiveBlock.dateGranularity; + } + + // /** + // * + // */ + // changeset(): string | undefined { + // if (this.#changeset === undefined) return; + // return this.primitiveBlock.getString(this.#changeset); + // } + + /** + * + */ + user(): string | undefined { + if (this.#userSid === undefined) return; + return this.primitiveBlock.getString(this.#userSid); + } + + /** + * @param tag + * @param info + * @param pbf + */ + #readLayer(tag: number, info: Info, pbf: Protobuf): void { + if (tag === 1) info.#version = pbf.readSVarint(); + else if (tag === 2) info.#timestamp = pbf.readSVarint(); + else if (tag === 3) info.#changeset = pbf.readSVarint(); + else if (tag === 4) info.#uid = pbf.readSVarint(); + else if (tag === 5) info.#userSid = pbf.readVarint(); + else if (tag === 6) info.#visible = pbf.readBoolean(); + else throw new Error(`unknown tag ${tag}`); + } +} + +/** DenseInfo */ +export class DenseInfo { + version: number[] = []; + timestamp: number[] = []; // DELTA coded (millisec_stamp = timestamp*dateGranularity.) + changeset: number[] = []; + uid: number[] = []; // DELTA coded + userSid: number[] = []; // String IDs for usernames. DELTA coded + // The visible flag is used to store history information. It indicates that + // the current object version has been created by a delete operation on the + // OSM API. + // When a writer sets this flag, it MUST add a required_features tag with + // value "HistoricalInformation" to the HeaderBlock. + // If this flag is not available for some object it MUST be assumed to be + // true if the file has the required_features tag "HistoricalInformation" + // set. + visible?: boolean[] = []; + + /** + * @param primitiveBlock + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + pbf: Protobuf, + ) { + pbf.readMessage(this.#readLayer, this); + } + + /** + * @param allocator + */ + infos(): Info[] { + const res: Info[] = []; + let curTimestamp = 0; + let curUid = 0; + for (let i = 0; i < this.version.length; i++) { + curTimestamp += this.timestamp[i]; + curUid += this.uid[i]; + res[i] = Info.fromDense( + this.primitiveBlock, + this.version[i], + curTimestamp, + this.changeset[i], + curUid, + this.userSid[i], + this.getVisible(i), + ); + } + return res; + } + + /** + * @param i + */ + getVisible(i: number): undefined | boolean { + if (this.visible === undefined) return; + return this.visible?.[i]; + } + + /** + * @param tag + * @param di + * @param pbf + */ + #readLayer(tag: number, di: DenseInfo, pbf: Protobuf): void { + if (tag === 1) di.version = pbf.readPackedSVarint(); + else if (tag === 2) di.timestamp = pbf.readPackedSVarint(); + else if (tag === 3) di.changeset = pbf.readPackedSVarint(); + else if (tag === 4) di.uid = pbf.readPackedSVarint(); + else if (tag === 5) di.userSid = pbf.readPackedVarint(); + else if (tag === 6) di.visible = pbf.readPackedBoolean(); + else throw new Error(`unknown tag ${tag}`); + } +} diff --git a/src/readers/osm/info.zig b/src/readers/osm/info.zig deleted file mode 100644 index 071d61f2..00000000 --- a/src/readers/osm/info.zig +++ /dev/null @@ -1,141 +0,0 @@ -const std = @import("std"); -const Protobuf = @import("pbf").Protobuf; -const Node = @import("node.zig").Node; -const PrimitiveBlock = @import("primitive.zig").PrimitiveBlock; - -// Optional metadata that may be included into each primitive. -pub const Info = struct { - version: ?i32 = null, - _timestamp: ?i64 = null, // (millisec_stamp = timestamp*date_granularity.) - _changeset: ?i64 = null, - uid: ?i32 = null, - user_sid: ?i32 = null, // String IDs for usernames. - // The visible flag is used to store history information. It indicates that - // the current object version has been created by a delete operation on the - // OSM API. - // When a writer sets this flag, it MUST add a required_features tag with - // value "HistoricalInformation" to the HeaderBlock. - // If this flag is not available for some object it MUST be assumed to be - // true if the file has the required_features tag "HistoricalInformation" - // set. - visible: ?bool = null, - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !Info { - var info = Info{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(Info, &info, readLayer); - return info; - } - - pub fn FromDense( - version_: i32, - timestamp_: i64, - changeset_: i64, - uid_: i32, - user_sid_: i32, - visible_: ?bool, - primitiveBlock_: *PrimitiveBlock, - ) Info { - return Info{ - .version = version_, - ._timestamp = timestamp_, - ._changeset = changeset_, - .uid = uid_, - .user_sid = user_sid_, - .visible = visible_, - .primitiveBlock = primitiveBlock_, - }; - } - - pub fn timeStamp(self: *Self) ?i64 { - if (self._timestamp == null) return null; - return self._timestamp + self.primitiveBlock.date_granularity; - } - - pub fn changeset(self: *Self) ?i64 { - if (self._changeset == null) return null; - return self.primitiveBlock.stringtable.get(self._changeset); - } - - pub fn user(self: *Self) ?[]const u8 { - if (self.user_sid == null) return null; - return self.primitiveBlock.stringtable.get(self.user_sid); - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.version = pbf.readVarint(i32), - 2 => self._timestamp = pbf.readVarint(i64), - 3 => self._changeset = pbf.readVarint(i64), - 4 => self.uid = pbf.readSVarint(i32), - 5 => self.user_sid = pbf.readVarint(i32), - 6 => self.visible = pbf.readVarint(bool), - else => unreachable, - } - } -}; - -/// DenseInfo -pub const DenseInfo = struct { - version: []i32 = undefined, - _timestamp: []i64 = undefined, // DELTA coded (millisec_stamp = timestamp*date_granularity.) - _changeset: []i64 = undefined, - uid: []i32 = undefined, // DELTA coded - user_sid: []i32 = undefined, // String IDs for usernames. DELTA coded - // The visible flag is used to store history information. It indicates that - // the current object version has been created by a delete operation on the - // OSM API. - // When a writer sets this flag, it MUST add a required_features tag with - // value "HistoricalInformation" to the HeaderBlock. - // If this flag is not available for some object it MUST be assumed to be - // true if the file has the required_features tag "HistoricalInformation" - // set. - visible: ?[]bool = null, - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !DenseInfo { - var denseNode = DenseInfo{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(DenseInfo, &denseNode, readLayer); - return denseNode; - } - - pub fn infos(self: Self, allocator: std.mem.Allocator) ![]Info { - var res = try allocator.alloc(Info, self.version.len); - var i: usize = 0; - var curTimestamp: i64 = 0; - var curUid: i32 = 0; - while (i < self.version.len) : (i += 1) { - curTimestamp += self._timestamp[i]; - curUid += self.uid[i]; - res[i] = Info.FromDense( - self.version[i], - curTimestamp, - self._changeset[i], - curUid, - self.user_sid[i], - self.getVisible(i), - self.primitiveBlock, - ); - } - return res; - } - - fn getVisible(self: Self, i: usize) ?bool { - if (self.visible == null) return null; - return self.visible.?[i]; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.version = try pbf.readPacked(i32), - 2 => self._timestamp = try pbf.readPackedS(i64), - 3 => self._changeset = try pbf.readPackedS(i64), - 4 => self.uid = try pbf.readPackedS(i32), - 5 => self.user_sid = try pbf.readPackedS(i32), - 6 => self.visible = try pbf.readPacked(bool), - else => unreachable, - } - } -}; diff --git a/src/readers/osm/main.zig b/src/readers/osm/main.zig deleted file mode 100644 index c858c741..00000000 --- a/src/readers/osm/main.zig +++ /dev/null @@ -1,374 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const assert = std.debug.assert; -const ArrayList = std.ArrayList; -const Thread = std.Thread; -const StringHashMap = std.StringHashMap; -const Protobuf = @import("pbf").Protobuf; -const node = @import("node.zig"); -pub const Node = node.Node; -const NodeHandle = node.NodeHandle; -const way = @import("way.zig"); -pub const Way = way.Way; -const WayHandle = way.WayHandle; -const relation = @import("relation.zig"); -pub const Relation = relation.Relation; -const RelationHandle = relation.RelationHandle; -const fileFormat = @import("fileFormat.zig"); -const BlobHeader = fileFormat.BlobHeader; -const Blob = fileFormat.Blob; -const primitive = @import("primitive.zig"); -const processBlock = primitive.processBlock; -const PrimitiveBlock = primitive.PrimitiveBlock; -pub const ChangeSet = primitive.ChangeSet; -const ChangeSetHandle = primitive.ChangeSetHandle; -const managers = @import("managers.zig"); -const DataManagerType = managers.DataManagerType; -const DataManager = managers.DataManager; -const io = std.io; -const BufferedReader = io.BufferedReader; - -pub const TagFilter = struct { - filters: StringHashMap(?[]const u8), - nodeFilters: StringHashMap(?[]const u8), - wayFilters: StringHashMap(?[]const u8), - relationFilters: StringHashMap(?[]const u8), - const Self = @This(); - const FilterType = enum { - All, - Node, - Way, - Relation, - }; - - pub fn Init(allocator: std.mem.Allocator) !Self { - return .{ - .filters = StringHashMap(?[]const u8).init(allocator), - .nodeFilters = StringHashMap(?[]const u8).init(allocator), - .wayFilters = StringHashMap(?[]const u8).init(allocator), - .relationFilters = StringHashMap(?[]const u8).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.filters.deinit(); - self.nodeFilters.deinit(); - self.wayFilters.deinit(); - self.relationFilters.deinit(); - } - - pub fn addFilter(self: *Self, filterType: FilterType, key: []const u8, value: ?[]const u8) !void { - switch (filterType) { - .All => try self.filters.put(key, value), - .Node => try self.nodeFilters.put(key, value), - .Way => try self.wayFilters.put(key, value), - .Relation => try self.relationFilters.put(key, value), - } - } - - pub fn matchFound(self: *Self, filterType: FilterType, key: []const u8, value: []const u8) bool { - const curFilters = switch (filterType) { - .Node => &self.nodeFilters, - .Way => &self.wayFilters, - .Relation => &self.relationFilters, - .All => unreachable, - }; - // check all filters first - if (self.filters.contains(key)) { - const filterValue = self.filters.get(key); - if (filterValue != null and - (filterValue.? == null or std.mem.eql(u8, filterValue.?.?, value))) return true; - } - // check type-specific filters - if (curFilters.contains(key)) { - const filterValue = curFilters.get(key); - if (filterValue != null and - (filterValue.? == null or std.mem.eql(u8, filterValue.?.?, value))) return true; - } - return false; - } -}; - -pub const Options = struct { - // if true, remove nodes that have no tags - removeEmptyNodes: bool = true, - tagFilter: ?*TagFilter = null, - manager: DataManagerType = .Memory, - threads: usize = 1, - mapSize: usize = 128 * 1024 * 1024, -}; - -pub fn CallBacks(comptime C: type) type { - return struct { - nodeHandle: ?NodeHandle(C) = null, - wayHandle: ?WayHandle(C) = null, - relationHandle: ?RelationHandle(C) = null, - changesetHandle: ?ChangeSetHandle(C) = null, - }; -} - -const FileReader = io.Reader(std.fs.File, std.fs.File.ReadError, std.fs.File.read); -const BufReaderContainer = BufferedReader(4096, FileReader); -const BufferReader = io.Reader(*BufReaderContainer, std.fs.File.ReadError, BufReaderContainer.read); - -pub const OSMReader = struct { - options: *const Options, - memoryManager: DataManager, - threads: ArrayList(Thread), - reader: *BufferReader, - file: *std.fs.File, - fileSize: u64, - arena: *std.heap.ArenaAllocator, - // when reading from a file - readMut: std.Thread.Mutex = .{}, - // when checking if all nodes have been read - nodeMut: std.Thread.Mutex = .{}, - nodeCond: std.Thread.Condition = std.Thread.Condition{}, - // when checking if all ways have been read - wayMut: std.Thread.Mutex = .{}, - wayCond: std.Thread.Condition = std.Thread.Condition{}, - // count of threads that parsed all nodes - parsedNodes: usize = 0, - // count of threads that parsed all ways - parsedWays: usize = 0, - const Self = @This(); - pub fn Init( - file_: *std.fs.File, - reader_: *BufferReader, - options_: *const Options, - arena_: *std.heap.ArenaAllocator, - ) !OSMReader { - return .{ - .options = options_, - .memoryManager = switch (options_.manager) { - .Memory => .{ .memory = try managers.MemoryManager.Init(arena_) }, - .File => .{ .file = try managers.FileManager.Init(options_.mapSize) }, - }, - .threads = ArrayList(Thread).init(arena_.allocator()), - .reader = reader_, - .file = file_, - .fileSize = try file_.getEndPos(), - .arena = arena_, - .parsedNodes = if (options_.threads == 1) 1 else 0, - }; - } - - pub fn deinit(self: *OSMReader) void { - self.threads.deinit(); - self.memoryManager.deinit(); - defer self.file.close(); - } - - /// OSM data comes in as a series of blocks. Each block is a blob that contains a - /// serialized primitive block. The primitive block contains a list of nodes, ways, - /// and relations. However, the order is always the same, nodes will always come first. - /// This means as soon as we hit a way or relation (and we are using threads) we need - /// to wait for all other threads to finish reading in nodes. - pub fn awaitNodeBlock(self: *Self) void { - if (self.parsedNodes >= self.options.threads) return; - self.nodeMut.lock(); - defer self.nodeMut.unlock(); - self.parsedNodes += 1; - // if we are single threaded, we don't need to wait for the node lock - while (self.parsedNodes < self.options.threads) { - self.nodeCond.wait(&self.nodeMut); - } - // the last thread to finish readings nodes will signal the other threads to continue - self.nodeCond.signal(); - } - - /// to expound upon the above function, after nodes are ways. So we need to wait for all - /// threads to finish reading nodes and ways before we can process relations. - pub fn awaitWayBlock(self: *Self) void { - if (self.parsedWays >= self.options.threads) return; - self.wayMut.lock(); - defer self.wayMut.unlock(); - self.parsedWays += 1; - // if we are single threaded, we don't need to wait for the way lock - while (self.parsedWays < self.options.threads) { - self.wayCond.wait(&self.wayMut); - } - // the last thread to finish readings ways will signal the other threads to continue - self.wayCond.signal(); - } - - pub fn processBlocksMulti(self: *Self, ctx: anytype, callbacks: *const CallBacks(@TypeOf(ctx))) !void { - var t: usize = 0; - while (t < self.options.threads) : (t += 1) { - // Start a new thread and pass the context to the worker thread. - try self.threads.append(try std.Thread.spawn(.{}, OSMReader.processBlocks, .{ self, ctx, callbacks })); - } - // lastly wait for threads to join - for (self.threads.items) |thread| thread.join(); - } - - pub fn processBlocks(self: *Self, ctx: anytype, callbacks: *const CallBacks(@TypeOf(ctx))) !void { - while (try self.next(false)) |blob| { - try self.readBlob(blob, ctx, callbacks); - } - // corner case: a thread never hits a way or relation (because the dataset is small), - // so it never signals the other threads - self.awaitNodeBlock(); - self.awaitWayBlock(); - } - - /// NOTE: If null is returned, all memory is already freed. However,if a - /// blob is returned, it is the caller's responsibility to free the memory. - pub fn next(self: *Self, isHeader: bool) !?[]u8 { - // ensure we don't read file contents at the same time - self.readMut.lock(); - defer self.readMut.unlock(); - // if we've already read all the data, return null - if (try self.file.getPos() >= self.fileSize) return null; - - // prepare a local allocator - const allocator = self.arena.child_allocator; - var localArena = std.heap.ArenaAllocator.init(allocator); - defer localArena.deinit(); - const localAllocator = localArena.allocator(); - - // STEP 1: Get blob size - // read length of current blob - const length = try self.reader.readInt(i32, .Big); - const blobHeaderData = try localAllocator.alloc(u8, @as(usize, @intCast(length))); - _ = try self.reader.read(blobHeaderData); - // build a blob header - var pbf = try Protobuf.init(blobHeaderData, &localArena); - const blobHeader = try BlobHeader.Init(&pbf); - // get size of actual blob - const compressedBlobLength = blobHeader.datasize; - - // STEP 2: Get blob data - const compressedBlobData = try allocator.alloc(u8, @as(usize, @intCast(compressedBlobLength))); - const readBytes = try self.reader.read(compressedBlobData); - assert(readBytes == compressedBlobLength); - - // if we're just reading the header, we can stop here - if (isHeader) { - allocator.free(compressedBlobData); - return null; - } - - return compressedBlobData; - } - - fn readBlob( - self: *Self, - compressedBlobData: []u8, - ctx: anytype, - callbacks: *const CallBacks(@TypeOf(ctx)), - ) !void { - const allocator = self.arena.child_allocator; - defer allocator.free(compressedBlobData); - - var localArena = std.heap.ArenaAllocator.init(allocator); - defer localArena.deinit(); - // Blob data is PBF encoded and ?compressed, so we need to parse & decompress it first - var pbf = try Protobuf.init(compressedBlobData, &localArena); - const blob = try Blob.Init(&pbf); - pbf = try Protobuf.init(blob.data, &localArena); - - // Parse the PrimitiveBlock and read its contents. - // all nodes/ways/relations that can be filtered already are on invocation. - var primitiveBlock = try PrimitiveBlock.Init( - &pbf, - self.options, - &self.memoryManager, - ); - try processBlock(self, ctx, callbacks, &primitiveBlock); - } -}; - -pub fn readOSM( - input: [:0]const u8, - ctx: anytype, - callbacks: CallBacks(@TypeOf(ctx)), - options: Options, - allocator: std.mem.Allocator, -) !void { - assert(options.threads > 0); - assert(options.threads <= try Thread.getCpuCount()); - // setup primary variables - var globalArena = std.heap.ArenaAllocator.init(allocator); - defer globalArena.deinit(); - - // pull in the file and prep a reader - var file = try std.fs.cwd().openFile(input, .{}); - var bufReader = std.io.bufferedReader(file.reader()); - var reader = bufReader.reader(); - - // initialize the OSMReader - var osmReader = try OSMReader.Init(&file, &reader, &options, &globalArena); - defer osmReader.deinit(); - - // read off the headerBlock first - _ = try osmReader.next(true); - // now we have the OSMReader read the rest of the file which is a series of blobs - // as PrimitiveBlocks. We create a distinction of whether we're using multiple threads - // or not so that we can use this module in WASM. - if (options.threads > 1) { - try osmReader.processBlocksMulti(ctx, &callbacks); - } else { - try osmReader.processBlocks(ctx, &callbacks); - } -} - -test "parse a small OSM file" { - var threadSafeTestingAlloc = std.heap.ThreadSafeAllocator{ .child_allocator = testing.allocator }; - const allocator = threadSafeTestingAlloc.allocator(); - try readOSM( - "src/osm/fixtures/andorra-latest.osm.pbf", - TestCTX{ .allocator = allocator }, - CallBacks(TestCTX){ - .nodeHandle = testNodeHandle, - .wayHandle = testWayHandle, - .relationHandle = testRelationHandle, - }, - .{ - .threads = 4, - // .manager = .File, - }, - allocator, - ); -} - -const TestCTX = struct { - allocator: std.mem.Allocator, -}; - -fn testNodeHandle(ctx: TestCTX, n: *Node) anyerror!void { - // _ = n.geometry(); - // var tags = try n.tags(ctx.allocator); - // defer tags.deinit(); - // var tagStr = try n.tagsAsJSONString(ctx.allocator); - // defer ctx.allocator.free(tagStr); - - var tmpArena = std.heap.ArenaAllocator.init(ctx.allocator); - defer tmpArena.deinit(); - _ = try n.toGeoJSON(&tmpArena); - // _ = try n.toS2JSON(&tmpArena); -} - -fn testWayHandle(ctx: TestCTX, w: *Way) anyerror!void { - // var nodes = try w.nodes(ctx.allocator); - // if (nodes != null) { - // defer ctx.allocator.free(nodes.?); - // } - - var tmpArena = std.heap.ArenaAllocator.init(ctx.allocator); - defer tmpArena.deinit(); - _ = try w.toGeoJSON(&tmpArena, true); - // _ = try w.toS2JSON(&tmpArena, true); -} - -fn testRelationHandle(ctx: TestCTX, r: *Relation) anyerror!void { - // var members = try r.members(ctx.allocator); - // if (members != null) { - // defer ctx.allocator.free(members.?); - // } - - var tmpArena = std.heap.ArenaAllocator.init(ctx.allocator); - defer tmpArena.deinit(); - _ = try r.toGeoJSON(&tmpArena); - // _ = try r.toS2JSON(&tmpArena); -} diff --git a/src/readers/osm/managers.zig b/src/readers/osm/managers.zig deleted file mode 100644 index eb825967..00000000 --- a/src/readers/osm/managers.zig +++ /dev/null @@ -1,303 +0,0 @@ -const std = @import("std"); -const lmdb = @import("lmdb"); -const Environment = lmdb.Environment; -const node = @import("node.zig"); -const NodeGeometry = node.NodeGeometry; -const NodeMember = node.NodeMember; -const way = @import("way.zig"); -const WayMember = way.WayMember; - -pub const DataManagerType = enum { - File, - Memory, -}; - -pub const DataManager = union(enum) { - file: FileManager, - memory: MemoryManager, - const Self = @This(); - - pub fn deinit(self: *Self) void { - switch (self.*) { - .file => |*f| f.deinit(), - .memory => |*m| m.deinit(), - } - } - - pub fn getNode(self: *Self, key: i64) !?NodeGeometry { - switch (self.*) { - .file => |*f| return try f.getNode(key), - .memory => |*m| return m.getNode(key), - } - } - - pub fn getNodeMember(self: *Self, key: i64, allocator: std.mem.Allocator) !?NodeMember { - switch (self.*) { - .file => |*f| return try f.getNodeMember(key, allocator), - .memory => |*m| return m.getNodeMember(key), - } - } - - pub fn putNode(self: *Self, key: i64, val: NodeGeometry, props: ?[]const u8) !void { - switch (self.*) { - .file => |*f| try f.putNode(key, val, props), - .memory => |*m| try m.putNode(key, val, props), - } - } - - pub fn getWayMember(self: *Self, key: i64, allocator: std.mem.Allocator) !?WayMember { - switch (self.*) { - .file => |*f| return try f.getWay(key, allocator), - .memory => |*m| return m.getWay(key), - } - } - - pub fn putWay(self: *Self, key: i64, val: []i64, props: ?[]const u8) !void { - switch (self.*) { - .file => |*f| try f.putWay(key, val, props), - .memory => |*m| try m.putWay(key, val, props), - } - } -}; - -fn clearExistingFiles() void { - std.fs.deleteFileAbsolute("/tmp/node_env_/lock.mdb") catch {}; - std.fs.deleteFileAbsolute("/tmp/node_env_/data.mdb") catch {}; - std.fs.deleteDirAbsolute("/tmp/node_env_") catch {}; - std.fs.deleteFileAbsolute("/tmp/node_prop_/lock.mdb") catch {}; - std.fs.deleteFileAbsolute("/tmp/node_prop_/data.mdb") catch {}; - std.fs.deleteDirAbsolute("/tmp/node_prop_") catch {}; - std.fs.deleteFileAbsolute("/tmp/way_env_/lock.mdb") catch {}; - std.fs.deleteFileAbsolute("/tmp/way_env_/data.mdb") catch {}; - std.fs.deleteDirAbsolute("/tmp/way_env_") catch {}; - std.fs.deleteFileAbsolute("/tmp/way_prop_/lock.mdb") catch {}; - std.fs.deleteFileAbsolute("/tmp/way_prop_/data.mdb") catch {}; - std.fs.deleteDirAbsolute("/tmp/way_prop_") catch {}; -} - -pub const FileManager = struct { - nodeEnv: Environment(i64, NodeGeometry), - nodeProperties: Environment(i64, []const u8), // JSON string - wayEnv: Environment(i64, []u8), - wayProperties: Environment(i64, []const u8), // JSON string - pub fn Init(mapSize: usize) !FileManager { - clearExistingFiles(); - var bufNodeEnv: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var bufNodeProp: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var bufWayEnv: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var bufWayProp: [std.fs.MAX_PATH_BYTES]u8 = undefined; - try std.fs.makeDirAbsolute("/tmp/node_env_"); - try std.fs.makeDirAbsolute("/tmp/node_prop_"); - try std.fs.makeDirAbsolute("/tmp/way_env_"); - try std.fs.makeDirAbsolute("/tmp/way_prop_"); - const nodeEnvPath = try std.fs.realpath("/tmp/node_env_/", &bufNodeEnv); - const nodePropPath = try std.fs.realpath("/tmp/node_prop_/", &bufNodeProp); - const wayEnvPath = try std.fs.realpath("/tmp/way_env_/", &bufWayEnv); - const wayPropPath = try std.fs.realpath("/tmp/way_prop_/", &bufWayProp); - return FileManager{ - .nodeEnv = try Environment(i64, NodeGeometry).init(nodeEnvPath, .{}), - .nodeProperties = try Environment(i64, []const u8).init(nodePropPath, .{}), - .wayEnv = try Environment(i64, []u8).init(wayEnvPath, .{ - .map_size = mapSize, - }), - .wayProperties = try Environment(i64, []const u8).init(wayPropPath, .{}), - }; - } - - pub fn deinit(self: *FileManager) void { - clearExistingFiles(); - self.nodeEnv.deinit(); - } - - pub fn getNode(self: *FileManager, key: i64) !?NodeGeometry { - const tx = try self.nodeEnv.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.nodeEnv); - - return try tx.getMaybeNull(db, key); - } - - pub fn getNodeProperties(self: *FileManager, key: i64) !?[]const u8 { - const tx = try self.nodeProperties.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.nodeProperties); - - return try tx.getMaybeNull(db, key); - } - - pub fn getNodeMember(self: *FileManager, key: i64, allocator: std.mem.Allocator) !?NodeMember { - const geometry = try self.getNode(key); - if (geometry == null) return null; - const props = try self.getNodeProperties(key); - if (props != null) { - const propsClone = try allocator.alloc(u8, props.?.len); - @memcpy(propsClone, props.?); - return NodeMember.Init(key, geometry.?.lat, geometry.?.lon, propsClone); - } - return NodeMember.Init(key, geometry.?.lat, geometry.?.lon, null); - } - - pub fn putNodeGeometry(self: *FileManager, key: i64, val: NodeGeometry) !void { - const tx = try self.nodeEnv.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.nodeEnv); - - try tx.put(db, key, val, .{}); - } - - pub fn putNodeProperties(self: *FileManager, key: i64, val: []const u8) !void { - const tx = try self.nodeProperties.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.nodeProperties); - - try tx.put(db, key, val, .{}); - } - - pub fn putNode(self: *FileManager, key: i64, val: NodeGeometry, props: ?[]const u8) !void { - try self.putNodeGeometry(key, val); - if (props != null) try self.putNodeProperties(key, props.?); - } - - pub fn getWayProperties(self: *FileManager, key: i64) !?[]const u8 { - const tx = try self.wayProperties.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.wayProperties); - - return try tx.getMaybeNull(db, key); - } - - pub fn getWayRefs(self: *FileManager, key: i64) !?[]u8 { - const tx = try self.wayEnv.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.wayEnv); - - return try tx.getMaybeNull(db, key); - } - - pub fn getWay(self: *FileManager, key: i64, allocator: std.mem.Allocator) !?WayMember { - const refs_ = try self.getWayRefs(key); - if (refs_ == null) return null; - const valSlice = @as([*]i64, @ptrCast(@alignCast(refs_.?)))[0..refs_.?.len]; - const refs = try allocator.alloc(i64, valSlice.len); - @memcpy(refs, valSlice); - // get the way properties - const props = try self.getWayProperties(key); - if (props != null) { - const propsClone = try allocator.alloc(u8, props.?.len); - @memcpy(propsClone, props.?); - return WayMember.Init(key, refs, propsClone); - } - return WayMember.Init(key, refs, null); - } - - pub fn putWayGeometry(self: *FileManager, key: i64, val: []i64) !void { - const tx = try self.wayEnv.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.wayEnv); - - const valSlice = @as([*]u8, @ptrCast(@alignCast(val)))[0 .. val.len * @sizeOf(i64)]; - tx.put(db, key, valSlice, .{}) catch |err| { - std.debug.print("ERROR: {}\n", .{err}); - return err; - }; - } - - pub fn putWayProperties(self: *FileManager, key: i64, val: []const u8) !void { - const tx = try self.wayProperties.begin(.{}); - errdefer tx.deinit(); - defer tx.commit() catch {}; - - const db = try tx.open(.{}); - defer db.close(self.wayProperties); - - try tx.put(db, key, val, .{}); - } - - pub fn putWay(self: *FileManager, key: i64, val: []i64, props: ?[]const u8) !void { - try self.putWayGeometry(key, val); - if (props != null) try self.putWayProperties(key, props.?); - } -}; - -pub const MemoryManager = struct { - nodeMap: std.AutoHashMap(i64, NodeMember), - wayMap: std.AutoHashMap(i64, WayMember), - mutex: std.Thread.Mutex = .{}, - arena: *std.heap.ArenaAllocator, - pub fn Init(arena: *std.heap.ArenaAllocator) !MemoryManager { - return MemoryManager{ - .nodeMap = std.AutoHashMap(i64, NodeMember).init(arena.allocator()), - .wayMap = std.AutoHashMap(i64, WayMember).init(arena.allocator()), - .arena = arena, - }; - } - - pub fn deinit(self: *MemoryManager) void { - self.nodeMap.deinit(); - } - - pub fn getNode(self: *MemoryManager, key: i64) ?NodeGeometry { - const member = self.nodeMap.get(key); - if (member == null) return null; - return NodeGeometry{ .lat = member.?.lat, .lon = member.?.lon }; - } - - pub fn getNodeMember(self: *MemoryManager, key: i64) ?NodeMember { - return self.nodeMap.get(key); - } - - pub fn putNode(self: *MemoryManager, key: i64, val: NodeGeometry, props: ?[]const u8) !void { - self.mutex.lock(); - defer self.mutex.unlock(); - var clone: ?[]const u8 = null; - if (props != null) { - var allocator = self.arena.allocator(); - const cl = try allocator.alloc(u8, props.?.len); - @memcpy(cl, props.?); - clone = cl; - } - const nodeMember: NodeMember = NodeMember.Init(key, val.lat, val.lon, clone); - try self.nodeMap.put(key, nodeMember); - } - - pub fn getWay(self: *MemoryManager, key: i64) ?WayMember { - return self.wayMap.get(key); - } - - pub fn putWay(self: *MemoryManager, key: i64, val: []i64, props: ?[]const u8) !void { - self.mutex.lock(); - defer self.mutex.unlock(); - var allocator = self.arena.allocator(); - var propClone: ?[]const u8 = null; - if (props != null) { - const pc = try allocator.alloc(u8, props.?.len); - @memcpy(pc, props.?); - propClone = pc; - } - const valClone = try allocator.alloc(i64, val.len); - @memcpy(valClone, val); - const wayMember = WayMember.Init(key, valClone, propClone); - try self.wayMap.put(key, wayMember); - } -}; diff --git a/src/readers/osm/node.ts b/src/readers/osm/node.ts new file mode 100644 index 00000000..032f23b6 --- /dev/null +++ b/src/readers/osm/node.ts @@ -0,0 +1,209 @@ +import { DenseInfo, Info } from './info'; + +import type { PrimitiveBlock } from './primitive'; +import type { Pbf as Protobuf } from 'open-vector-tile'; +import type { InfoBlock, OSMReader } from '.'; + +import type { VectorFeature, VectorPoint } from 's2-tools/geometry'; + +/** + * + */ +export class Node { + id = 1; + info?: Info; + lat = 0.0; + lon = 0.0; + #keys: number[] = []; + #vals: number[] = []; + + /** + * @param primitiveBlock + * @param reader + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + public reader: OSMReader, + pbf?: Protobuf, + ) { + this.primitiveBlock = primitiveBlock; + if (pbf !== undefined) pbf.readMessage(this.#readLayer, this); + } + + /** + * @param id + * @param info + * @param keys + * @param vals + * @param lat + * @param lon + * @param pb + * @param reader + */ + static fromDense( + id: number, + info: Info | undefined, + keys: number[], + vals: number[], + lat: number, + lon: number, + pb: PrimitiveBlock, + reader: OSMReader, + ): Node { + const node = new Node(pb, reader); + node.id = id; + node.info = info; + node.lat = 0.000000001 * (pb.latOffset + pb.granularity * lat); + node.lon = 0.000000001 * (pb.latOffset + pb.granularity * lon); + node.#keys = keys; + node.#vals = vals; + + return node; + } + + /** + * @param options + */ + isFilterable(): boolean { + const { primitiveBlock: pb, reader } = this; + const { tagFilter, removeEmptyNodes } = reader; + if (removeEmptyNodes && this.#keys.length === 0) return true; + if (tagFilter !== undefined) { + for (let i = 0; i < this.#keys.length; i++) { + const keyStr = pb.getString(this.#keys[i]); + const valStr = pb.getString(this.#vals[i]); + if (tagFilter.matchFound('Node', keyStr, valStr)) return false; + } + // if we make it here, we didn't find any matching tags + return true; + } + return false; + } + + /** + * + */ + properties(): Record { + return this.primitiveBlock.tags(this.#keys, this.#vals); + } + + /** + * @param tag + * @param node + * @param pbf + */ + #readLayer(tag: number, node: Node, pbf: Protobuf): void { + const { primitiveBlock: pb } = node; + + if (tag === 1) this.id = pbf.readVarint(); + else if (tag === 2) this.#keys = pbf.readPackedVarint(); + else if (tag === 3) this.#vals = pbf.readPackedVarint(); + else if (tag === 4) this.info = new Info(pb, pbf); + else if (tag === 8) this.lat = 0.000000001 * pb.latOffset + pb.granularity * pbf.readSVarint(); + else if (tag === 9) this.lon = 0.000000001 * pb.latOffset + pb.granularity * pbf.readSVarint(); + else throw new Error(`Unknown tag: ${tag}`); + } + + /** + * + */ + toVectorGeometry(): VectorPoint { + // TODO: if feature has altitude or something defining its z position, make feature 3D + return { x: this.lon, y: this.lat }; + } + + /** + * + */ + toVectorFeature(): VectorFeature { + return { + id: this.id, + type: 'VectorFeature', + properties: this.properties(), + geometry: { type: 'Point', is3D: false, coordinates: this.toVectorGeometry() }, + metadata: this.info?.toBlock(), + }; + } +} + +/** + * Used to densly represent a sequence of nodes that do not have any tags. + * We represent these nodes columnwise as five columns: ID's, lats, and + * lons, all delta coded. When metadata is not omitted, + * We encode keys & vals for all nodes as a single array of integers + * containing key-stringid and val-stringid, using a stringid of 0 as a + * delimiter between nodes. + * ( ( )* '0' )* + */ +export class DenseNodes { + ids: number[] = []; // DELTA coded + denseinfo?: DenseInfo; + lats: number[] = []; // DELTA coded + lons: number[] = []; // DELTA coded + // Special packing of keys and vals into one array. May be empty if all nodes in this block are tagless. + keysVals: number[] = []; + + /** + * @param reader + * @param pbf + * @param primitiveBlock + */ + constructor( + public primitiveBlock: PrimitiveBlock, + public reader: OSMReader, + pbf: Protobuf, + ) { + this.primitiveBlock = primitiveBlock; + pbf.readMessage(this.#readLayer, this); + } + + /** + * + */ + nodes(): Node[] { + const { primitiveBlock: pb, reader } = this; + const res: Node[] = []; + const infoMap = this.denseinfo?.infos(); + let j = 0; + let curId = 0; + let curLat = 0; + let curLon = 0; + for (let i = 0; i < this.ids.length; i++) { + const curInfo = infoMap?.[i]; + curId += this.ids[i]; + curLat += this.lats[i]; + curLon += this.lons[i]; + const keys: number[] = []; + const vals: number[] = []; + if (this.keysVals.length > 0) { + while (this.keysVals[j] != 0) { + keys.push(this.keysVals[j]); + vals.push(this.keysVals[j + 1]); + j += 2; + } + j += 1; + } + const node = Node.fromDense(curId, curInfo, keys, vals, curLat, curLon, pb, reader); + res.push(node); + } + + return res; + } + + /** + * @param tag + * @param denseNodes + * @param pbf + */ + #readLayer(tag: number, denseNodes: DenseNodes, pbf: Protobuf): void { + const { primitiveBlock: pb } = denseNodes; + + if (tag == 1) denseNodes.ids = pbf.readPackedSVarint(); + else if (tag == 5) denseNodes.denseinfo = new DenseInfo(pb, pbf); + else if (tag == 8) denseNodes.lats = pbf.readPackedSVarint(); + else if (tag == 9) denseNodes.lons = pbf.readPackedSVarint(); + else if (tag == 10) denseNodes.keysVals = pbf.readPackedVarint(); + else throw new Error('unknown tag ' + tag); + } +} diff --git a/src/readers/osm/node.zig b/src/readers/osm/node.zig deleted file mode 100644 index c4c8674f..00000000 --- a/src/readers/osm/node.zig +++ /dev/null @@ -1,297 +0,0 @@ -const std = @import("std"); -const ArrayList = std.ArrayList; -const StringHashMap = std.StringHashMap; -const Protobuf = @import("pbf").Protobuf; -const info = @import("info.zig"); -const Info = info.Info; -const DenseInfo = info.DenseInfo; -const PrimitiveBlock = @import("primitive.zig").PrimitiveBlock; -const Options = @import("main.zig").Options; -const json = @import("json"); -const Feature = json.Feature; -const S2Feature = json.S2Feature; -const S2 = @import("S2"); -const S2Latlon = S2.S2LatLng; -const XYZtoFaceST = S2.XYZtoFaceST; - -pub fn NodeHandle(comptime C: type) type { - return fn (ctx: C, node: *Node) anyerror!void; -} - -/// Used when either requesting geometry or to store away into a -/// memory manager. -pub const NodeGeometry = packed struct { - lon: f64, - lat: f64, -}; - -/// Nodes are stored away into a memory manager. This Member is -/// created when a relation requests said node. -pub const NodeMember = struct { - id: u64, - lon: f64, - lat: f64, - propStr: ?[]const u8 = null, - const Self = @This(); - - pub fn Init( - id_: i64, - lat_: f64, - lon_: f64, - propStr_: ?[]const u8, - ) NodeMember { - return NodeMember{ - .id = @as(u64, @intCast(id_)), - .lon = lon_, - .lat = lat_, - .propStr = propStr_, - }; - } - - pub fn toGeoJSON(self: Self, arena: *std.heap.ArenaAllocator) !Feature { - const allocator = arena.allocator(); - // coords - var coords = try allocator.alloc(f64, 2); - coords[0] = self.lon; - coords[1] = self.lat; - - return Feature{ - .id = self.id, - .geometry = json.Geometry{ .point = coords }, - .properties = try json.Value.FromString(self.propStr orelse "{}", arena), - }; - } - - pub fn toS2JSON(self: Self, arena: *std.heap.ArenaAllocator) !S2Feature { - const allocator = arena.allocator(); - // update coords to S2 - var coords = try allocator.alloc(f64, 2); - var ll = S2Latlon.FromDegrees(self.lat, self.lon); - var s2point = ll.ToPoint(); - const face: u3 = XYZtoFaceST(&s2point, &coords[0], &coords[1]); - - return S2Feature{ - .id = self.id, - .face = face, - .geometry = json.Geometry{ .s2point = coords }, - .properties = try json.Value.FromString(self.propStr orelse "{}", arena), - }; - } -}; - -pub const Node = struct { - id: i64 = -1, - _info: ?Info = null, - _keys: []usize = &[_]usize{}, - _vals: []usize = &[_]usize{}, - lat: f64 = 0.0, - lon: f64 = 0.0, - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !Node { - var node = Node{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(Node, &node, readLayer); - return node; - } - - pub fn FromDense( - id_: i64, - info_: ?Info, - keys_: []usize, - vals_: []usize, - lat_: i64, - lon_: i64, - pb_: *PrimitiveBlock, - ) Node { - return Node{ - .id = id_, - ._info = info_, - ._keys = keys_, - ._vals = vals_, - .lat = 0.000000001 * @as(f64, @floatFromInt(pb_.lat_offset + (pb_.granularity * lat_))), - .lon = 0.000000001 * @as(f64, @floatFromInt(pb_.lat_offset + (pb_.granularity * lon_))), - .primitiveBlock = pb_, - }; - } - - pub fn geometry(self: Self) NodeGeometry { - return NodeGeometry{ .lon = self.lon, .lat = self.lat }; - } - - pub fn isFilterable(self: Self, options: *const Options) bool { - if (options.removeEmptyNodes and self._keys.len == 0) return true; - if (options.tagFilter != null) { - var tagFilter = options.tagFilter.?; - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - const keyStr = self.primitiveBlock.getString(self._keys[i]); - const valStr = self.primitiveBlock.getString(self._vals[i]); - if (tagFilter.matchFound(.Node, keyStr, valStr)) return false; - } - // if we make it here, we didn't find any matching tags - return true; - } - return false; - } - - pub fn info(self: Self) ?Info { - if (self._info == null) return null; - self._info.?.primitiveBlock = self.primitiveBlock; - return self._info; - } - - pub fn tags(self: Self, allocator: std.mem.Allocator) !StringHashMap([]const u8) { - return try self.primitiveBlock.tags(self._keys, self._vals, allocator); - } - - pub fn tagsAsJSONString(self: Self, allocator: std.mem.Allocator) !?[]const u8 { - return try self.primitiveBlock.tagsAsJSONString(self._keys, self._vals, allocator); - } - - pub fn hasKey(self: Self, key: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key)) return true; - } - return false; - } - - pub fn hasKeyValue(self: Self, key: []const u8, val: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key) and - std.mem.eql(u8, self.primitiveBlock.getString(self._vals[i]), val)) return true; - } - return false; - } - - pub fn toGeoJSON(self: Self, arena: *std.heap.ArenaAllocator) !Feature { - const allocator = arena.allocator(); - // id - const id: ?u64 = if (self.id >= 0) @as(u64, @intCast(self.id)) else null; - // properties - const props = (try self.tagsAsJSONString(allocator)) orelse "{}"; - // coords - var coords = try allocator.alloc(f64, 2); - coords[0] = self.lon; - coords[1] = self.lat; - - return Feature{ - .id = id, - .geometry = json.Geometry{ .point = coords }, - .properties = try json.Value.FromString(props, arena), - }; - } - - pub fn toS2JSON(self: Self, arena: *std.heap.ArenaAllocator) !S2Feature { - const allocator = arena.allocator(); - // id - const id: ?u64 = if (self.id >= 0) @as(u64, @intCast(self.id)) else null; - // properties - const props = (try self.tagsAsJSONString(allocator)) orelse "{}"; - // update coords to S2 - var coords = try allocator.alloc(f64, 2); - var ll = S2Latlon.FromDegrees(self.lat, self.lon); - var s2point = ll.ToPoint(); - const face: u3 = XYZtoFaceST(&s2point, &coords[0], &coords[1]); - - return S2Feature{ - .id = id, - .face = face, - .geometry = json.Geometry{ .s2point = coords }, - .properties = try json.Value.FromString(props, arena), - }; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - const pb = self.primitiveBlock; - switch (tag) { - 1 => self.id = pbf.readVarint(i64), - 2 => self._keys = try pbf.readPacked(usize), - 3 => self._vals = try pbf.readPacked(usize), - 4 => self._info = try Info.Init(pbf, self.primitiveBlock), - 8 => self.lat = 0.000000001 * @as(f64, @floatFromInt(pb.lat_offset + (pb.granularity * pbf.readSVarint(i64)))), - 9 => self.lon = 0.000000001 * @as(f64, @floatFromInt(pb.lon_offset + (pb.granularity * pbf.readSVarint(i64)))), - else => unreachable, - } - } -}; - -// Used to densly represent a sequence of nodes that do not have any tags. -// We represent these nodes columnwise as five columns: ID's, lats, and -// lons, all delta coded. When metadata is not omitted, -// We encode keys & vals for all nodes as a single array of integers -// containing key-stringid and val-stringid, using a stringid of 0 as a -// delimiter between nodes. -// ( ( )* '0' )* -pub const DenseNodes = struct { - _id: []i64 = undefined, // DELTA coded - denseinfo: ?DenseInfo = null, - _lat: []i64 = undefined, // DELTA coded - _lon: []i64 = undefined, // DELTA coded - // Special packing of keys and vals into one array. May be empty if all nodes in this block are tagless. - keys_vals: []usize = undefined, - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !DenseNodes { - var denseNodes = DenseNodes{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(DenseNodes, &denseNodes, readLayer); - return denseNodes; - } - - pub fn nodes(self: Self, allocator: std.mem.Allocator) ![]Node { - var res = try ArrayList(Node).initCapacity(allocator, self._id.len); - const infoMap = if (self.denseinfo != null) - try self.denseinfo.?.infos(allocator) - else - null; - var i: usize = 0; - var j: usize = 0; - var curId: i64 = 0; - var curLat: i64 = 0; - var curLon: i64 = 0; - while (i < self._id.len) : (i += 1) { - const curInfo: ?Info = if (infoMap != null) - infoMap.?[i] - else - null; - curId += self._id[i]; - curLat += self._lat[i]; - curLon += self._lon[i]; - var keys = ArrayList(usize).init(allocator); - var vals = ArrayList(usize).init(allocator); - if (self.keys_vals.len > 0) { - while (self.keys_vals[j] != 0) : (j += 2) { - try keys.append(self.keys_vals[j]); - try vals.append(self.keys_vals[j + 1]); - } - j += 1; - } - const node = Node.FromDense( - curId, - curInfo, - try keys.toOwnedSlice(), - try vals.toOwnedSlice(), - curLat, - curLon, - self.primitiveBlock, - ); - res.appendAssumeCapacity(node); - } - - return try res.toOwnedSlice(); - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self._id = try pbf.readPackedS(i64), - 5 => self.denseinfo = try DenseInfo.Init(pbf, self.primitiveBlock), - 8 => self._lat = try pbf.readPackedS(i64), - 9 => self._lon = try pbf.readPackedS(i64), - 10 => self.keys_vals = try pbf.readPacked(usize), - else => unreachable, - } - } -}; diff --git a/src/readers/osm/primitive.ts b/src/readers/osm/primitive.ts new file mode 100644 index 00000000..abbf453f --- /dev/null +++ b/src/readers/osm/primitive.ts @@ -0,0 +1,185 @@ +import { Relation } from './relation'; +import { Way } from './way'; +import { DenseNodes, Node } from './node'; + +import type { OSMReader } from '.'; +import type { Pbf as Protobuf } from 'open-vector-tile'; + +/** + * NOTE: currently relations are stored, but we don't wait for the Block to store all relations + * before we start testing primtiveHandle against the data. This is a problem because + * relations reference eachother at times, and we need to be able to resolve those references + * before we can run relationHandle against the data. This isn't an important issue since + * in practice, all relations that reference eachother often produce garbage or unusable data. + * But it would be *nice* to fix this. Morbidly enough, the "BEST" solution is to treat relations + * like we do nodes and ways since relations could possibly reference eachother outside their own block. + * From a practical standpoint, I can't see this being worth the effort or memory/time cost. + */ +export class PrimitiveBlock { + stringtable!: StringTable; + primitiveGroups: PrimitiveGroup[] = []; + // Granularity, units of nanodegrees, used to store coordinates in this block. + granularity = 100; + // Offset value between the output coordinates and the granularity grid in units of nanodegrees. + latOffset = 0; + lonOffset = 0; + // Granularity of dates, normally represented in units of milliseconds since the 1970 epoch. + dateGranularity = 1000; + + /** + * @param pbf + * @param reader + */ + constructor( + public pbf: Protobuf, + public reader: OSMReader, + ) { + pbf.readFields(this.#readLayer, this, 0); + } + + /** + * @param index + */ + getString(index: number): string { + return this.stringtable.get(index); + } + + /** + * @param keys + * @param values + */ + tags(keys: number[], values: number[]): Record { + const res: Record = {}; + for (let i = 0; i < keys.length; i++) { + res[this.getString(keys[i])] = this.getString(values[i]); + } + return res; + } + + /** + * @param tag + * @param primitiveBlock + * @param pb + * @param pbf + */ + #readLayer(tag: number, pb: PrimitiveBlock, pbf: Protobuf): void { + if (tag === 1) pb.stringtable = new StringTable(pbf); + else if (tag === 2) pb.primitiveGroups.push(new PrimitiveGroup(pb, pbf)); + else if (tag === 17) pb.granularity = pbf.readVarint(); + else if (tag === 18) pb.dateGranularity = pbf.readVarint(); + else if (tag === 19) pb.latOffset = pbf.readVarint(); + else if (tag === 20) pb.lonOffset = pbf.readVarint(); + else throw new Error(`unknown tag ${tag}`); + } +} + +/** Group of OSMPrimitives. All primitives in a group must be the same type. */ +export class PrimitiveGroup { + nodes: Node[] = []; + ways: Way[] = []; + relations: Relation[] = []; + changesets: ChangeSet[] = []; + /** + * @param primitiveBlock + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + public pbf: Protobuf, + ) { + pbf.readMessage(this.#readLayer, this); + } + + /** + * @param tag + * @param pg + * @param pbf + */ + #readLayer(tag: number, pg: PrimitiveGroup, pbf: Protobuf): void { + const { nodes, ways, relations, primitiveBlock } = pg; + const { reader } = primitiveBlock; + const { skipNodes, skipWays, skipRelations } = reader; + const skipWR = skipWays && skipRelations; + + if (tag === 1) { + const node = new Node(primitiveBlock, reader, pbf); + if (!skipWR) reader.nodes.set(node.id, node.toVectorGeometry()); + if (!node.isFilterable() || skipNodes) nodes.push(node); + } else if (tag === 2) { + const dn = new DenseNodes(primitiveBlock, reader, pbf); + for (const node of dn.nodes()) { + if (!skipWR) reader.nodes.set(node.id, node.toVectorGeometry()); + if (!node.isFilterable() || skipNodes) nodes.push(node); + } + } else if (tag === 3) { + if (skipWR) return; + const way = new Way(primitiveBlock, reader, pbf); + const wayLine = way.toVectorGeometry(); + if (wayLine !== undefined) reader.ways.set(way.id, wayLine); + if (!way.isFilterable() || skipWays) ways.push(way); + } else if (tag === 4) { + if (skipWR) return; + const relation = new Relation(primitiveBlock, reader, pbf); + if (!relation.isFilterable() || skipRelations) relations.push(relation); + } else if (tag === 5) { + this.changesets.push(new ChangeSet(pbf)); + } + } +} + +/** + * String table, contains the common strings in each block. + * Note that we reserve index '0' as a delimiter, so the entry at that + * index in the table is ALWAYS blank and unused. + * NOTE: OSM isn't safe and allows " inside of strings, so we have to replace them with ' + * NOTE: OSM isn't safe and allows \ at the end of strings, so we have to remove them so it can be properly parsed. + */ +export class StringTable { + strings: string[] = []; + + /** + * @param pbf + */ + constructor(pbf: Protobuf) { + pbf.readMessage(this.#readLayer, this); + } + + /** + * @param index + */ + get(index: number): string { + return this.strings[index]; + } + + /** + * @param tag + * @param st + * @param pbf + */ + #readLayer(tag: number, st: StringTable, pbf: Protobuf): void { + if (tag == 1) st.strings.push(pbf.readString()); + else throw new Error(`unknown tag ${tag}`); + } +} + +/** This is kept for backwards compatibility but not used anywhere. */ +export class ChangeSet { + id = 0; + + /** + * @param pbf + */ + constructor(pbf: Protobuf) { + pbf.readMessage(this.#readLayer, this); + } + + /** + * @param tag + * @param cs + * @param pbf + */ + #readLayer(tag: number, cs: ChangeSet, pbf: Protobuf): void { + if (tag == 1) cs.id = pbf.readVarint(); + else throw new Error(`unknown tag ${tag}`); + } +} diff --git a/src/readers/osm/primitive.zig b/src/readers/osm/primitive.zig deleted file mode 100644 index c7ba5c1f..00000000 --- a/src/readers/osm/primitive.zig +++ /dev/null @@ -1,277 +0,0 @@ -const std = @import("std"); -const ArrayList = std.ArrayList; -const AutoHashMap = std.AutoHashMap; -const StringHashMap = std.StringHashMap; -const Protobuf = @import("pbf").Protobuf; -const Info = @import("info.zig").Info; -const node = @import("node.zig"); -const main = @import("main.zig"); -const Options = main.Options; -const OSMReader = main.OSMReader; -const CallBacks = main.CallBacks; -const Node = node.Node; -const NodeGeometry = node.NodeGeometry; -const NodeMember = node.NodeMember; -const NodeHandle = node.NodeHandle; -const DenseNodes = node.DenseNodes; -const way = @import("way.zig"); -const Way = way.Way; -const WayMember = way.WayMember; -const WayHandle = way.WayHandle; -const relation = @import("relation.zig"); -const Relation = relation.Relation; -const RelationHandle = relation.RelationHandle; -const managers = @import("managers.zig"); -const DataManager = managers.DataManager; - -pub fn ChangeSetHandle(comptime C: type) type { - return fn (ctx: C, node: *ChangeSet) anyerror!void; -} - -/// We "manually" read PrimitiveGroup data because we want to inject -/// the osmReader, ctx, callbacks, and primitiveBlock into the readLayer function -pub fn processBlock( - osmReader: *OSMReader, - ctx: anytype, - callbacks: *const CallBacks(@TypeOf(ctx)), - primitiveBlock: *PrimitiveBlock, -) !void { - var pbf = primitiveBlock.pbf; - for (primitiveBlock.primitiveGroups_.items) |*pg| { - // set start position - pbf.setPos(pg.pos); - // read message: - const end = pbf.readValue() + pbf.pos; - while (pbf.pos < end) { - const field = pbf.readField(); - const startPos = pbf.pos; - try pg.readLayer(ctx, callbacks, primitiveBlock, osmReader, field.tag, pbf); - if (pbf.pos == startPos) pbf.skip(field.type); - } - } -} - -/// NOTE: currently relations_ are stored, but we don't wait for the Block to store all relations -/// before we start testing primtiveHandle against the data. This is a problem because -/// relations reference eachother at times, and we need to be able to resolve those references -/// before we can run relationHandle against the data. This isn't an important issue since -/// in practice, all relations that reference eachother often produce garbage or unusable data. -/// But it would be *nice* to fix this. Morbidly enough, the "BEST" solution is to treat relations -/// like we do nodes and ways since relations could possibly reference eachother outside their own block. -/// From a practical standpoint, I can't see this being worth the effort or memory/time cost. -pub const PrimitiveBlock = struct { - pbf: *Protobuf, - stringtable: StringTable = undefined, - primitiveGroups_: ArrayList(PrimitiveGroup), - relations_: AutoHashMap(i64, Relation), - // Granularity, units of nanodegrees, used to store coordinates in this block. - granularity: i64 = 100, - // Offset value between the output coordinates and the granularity grid in units of nanodegrees. - lat_offset: i64 = 0, - lon_offset: i64 = 0, - // Granularity of dates, normally represented in units of milliseconds since the 1970 epoch. - date_granularity: i32 = 1000, - options: *const Options, - manager: *DataManager, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, options_: *const Options, manager_: *DataManager) !PrimitiveBlock { - const allocator = pbf.arena.allocator(); - var primitiveBlock = PrimitiveBlock{ - .pbf = pbf, - .primitiveGroups_ = ArrayList(PrimitiveGroup).init(allocator), - .relations_ = AutoHashMap(i64, Relation).init(allocator), - .options = options_, - .manager = manager_, - }; - try pbf.readFields(PrimitiveBlock, &primitiveBlock, readLayer); - return primitiveBlock; - } - - pub fn getNode(self: *Self, index: i64) !?NodeGeometry { - return try self.manager.getNode(index); - } - - pub fn getNodeMember(self: *Self, index: i64, allocator: std.mem.Allocator) !?NodeMember { - return try self.manager.getNodeMember(index, allocator); - } - - pub fn getWayMember(self: *Self, index: i64, allocator: std.mem.Allocator) !?WayMember { - return try self.manager.getWayMember(index, allocator); - } - - pub fn getString(self: *Self, index: usize) []const u8 { - return self.stringtable.get(index); - } - - pub fn tags(self: *Self, keys: []usize, values: []usize, allocator: std.mem.Allocator) !StringHashMap([]const u8) { - var res = StringHashMap([]const u8).init(allocator); - var i: usize = 0; - while (i < keys.len) : (i += 1) { - try res.put(self.stringtable.get(keys[i]), self.stringtable.get(values[i])); - } - return res; - } - - pub fn tagsAsJSONString(self: *Self, keys: []usize, values: []usize, allocator: std.mem.Allocator) !?[]const u8 { - if (keys.len == 0) return null; - var res = ArrayList(u8).init(allocator); - var writer = res.writer(); - try writer.writeByte('{'); - var i: usize = 0; - while (i < keys.len) : (i += 1) { - try writer.writeByte('"'); - try writer.writeAll(self.stringtable.get(keys[i])); - try writer.writeAll("\":\""); - try writer.writeAll(self.stringtable.get(values[i])); - try writer.writeByte('"'); - if (i < keys.len - 1) try writer.writeByte(','); - } - try writer.writeByte('}'); - return try res.toOwnedSlice(); - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.stringtable = try StringTable.Init(pbf), - 2 => try self.primitiveGroups_.append(try PrimitiveGroup.Init(pbf)), - 17 => self.granularity = pbf.readVarint(i64), - 18 => self.date_granularity = pbf.readVarint(i32), - 19 => self.lat_offset = pbf.readVarint(i64), - 20 => self.lon_offset = pbf.readVarint(i64), - else => unreachable, - } - } -}; - -/// Group of OSMPrimitives. All primitives in a group must be the same type. -pub const PrimitiveGroup = struct { - // pos where to start reading this groups message in the PBF - pos: usize, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !PrimitiveGroup { - return PrimitiveGroup{ .pos = pbf.pos }; - } - - fn readLayer( - _: *Self, - ctx: anytype, - callbacks: *const CallBacks(@TypeOf(ctx)), - pb: *PrimitiveBlock, - osmReader: *OSMReader, - tag: u64, - pbf: *Protobuf, - ) !void { - const nodeHandle = callbacks.nodeHandle; - const wayHandle = callbacks.wayHandle; - const relationHandle = callbacks.relationHandle; - const changesetHandle = callbacks.changesetHandle; - const options = pb.options; - switch (tag) { - 1 => { - var n = try Node.Init(pbf, pb); - if (wayHandle != null or relationHandle != null) { - // ONLY store tags if relations are needed - const tags = if (relationHandle != null) try n.tagsAsJSONString(pbf.arena.allocator()) else null; - try pb.manager.putNode( - n.id, - n.geometry(), - tags, - ); - } - if (nodeHandle != null and !n.isFilterable(options)) try nodeHandle.?(ctx, &n); - }, - 2 => { - var dn = try DenseNodes.Init(pbf, pb); - const dnNodes = try dn.nodes(pbf.arena.allocator()); - for (dnNodes) |*n| { - if (wayHandle != null or relationHandle != null) { - const tags = if (relationHandle != null) try n.tagsAsJSONString(pbf.arena.allocator()) else null; - try pb.manager.putNode(n.id, n.geometry(), tags); - } - if (nodeHandle != null and !n.isFilterable(options)) try nodeHandle.?(ctx, n); - } - }, - 3 => { - osmReader.awaitNodeBlock(); - var w = try Way.Init(pbf, pb); - if (relationHandle != null) { - try pb.manager.putWay(w.id, w._refs, try w.tagsAsJSONString(pbf.arena.allocator())); - } - if (wayHandle != null and !w.isFilterable(options)) try wayHandle.?(ctx, &w); - }, - 4 => { - osmReader.awaitNodeBlock(); - osmReader.awaitWayBlock(); - var r = try Relation.Init(pbf, pb); - if (relationHandle != null and !r.isFilterable(options)) { - try pb.relations_.put(r.id, r); - try relationHandle.?(ctx, &r); - } - }, - 5 => { - var c = try ChangeSet.Init(pbf); - if (changesetHandle != null) try changesetHandle.?(ctx, &c); - }, - else => unreachable, - } - } -}; - -/// String table, contains the common strings in each block. -/// Note that we reserve index '0' as a delimiter, so the entry at that -/// index in the table is ALWAYS blank and unused. -/// NOTE: OSM isn't safe and allows " inside of strings, so we have to replace them with ' -/// NOTE: OSM isn't safe and allows \ at the end of strings, so we have to remove them so it can be properly parsed. -pub const StringTable = struct { - s: ArrayList([]const u8), - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !StringTable { - var stringTable = StringTable{ - .s = ArrayList([]const u8).init(pbf.arena.allocator()), - }; - try pbf.readMessage(StringTable, &stringTable, readLayer); - return stringTable; - } - - pub fn get(self: *Self, index: usize) []const u8 { - return self.s.items[index]; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => { - // unfortunately we have to clean up the strings here - const bytes = pbf.readBytes(); - // convert all " to ' and don't allow \ at the end of the string - for (bytes, 0..) |*b, idx| { - if (b.* == '"') b.* = '\''; - if (b.* == '\\' and idx == bytes.len - 1) b.* = ' '; - } - try self.s.append(bytes); - }, - else => unreachable, - } - } -}; - -/// This is kept for backwards compatibility but not used anywhere. -pub const ChangeSet = struct { - id: i64 = 0, - const Self = @This(); - - pub fn Init(pbf: *Protobuf) !ChangeSet { - var changeSet = ChangeSet{}; - try pbf.readMessage(ChangeSet, &changeSet, readLayer); - return changeSet; - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.id = pbf.readVarint(i64), - else => unreachable, - } - } -}; diff --git a/src/readers/osm/relation.ts b/src/readers/osm/relation.ts new file mode 100644 index 00000000..95f4d2ac --- /dev/null +++ b/src/readers/osm/relation.ts @@ -0,0 +1,339 @@ +import { Info, InfoBlock } from './info'; + +import type { OSMReader } from '.'; +import type { PrimitiveBlock } from './primitive'; +import type { Pbf as Protobuf } from 'open-vector-tile'; + +import type { + VectorFeature, + VectorGeometry, + VectorLineString, + VectorMultiLineString, + VectorMultiPolygon, + VectorPoint, + VectorPolygon, +} from 's2-tools/geometry'; + +/** + * + */ +enum MemberType { + Node = 0, + Way = 1, + Relation = 2, +} + +/** + * + */ +export enum RoleType { + Area, + Outer, + Inner, + From, + To, + Via, + Label, + AdminCentre, + Stop, + Empty, + Other, +} +/** + * @param name - string name of the role + * @returns the RoleType + */ +export function fromStringToRoleType(name: string): RoleType { + if (name === 'area') return RoleType.Area; + if (name === 'outer') return RoleType.Outer; + if (name === 'inner') return RoleType.Inner; + if (name === 'from') return RoleType.From; + if (name === 'to') return RoleType.To; + if (name === 'via') return RoleType.Via; + if (name === 'label') return RoleType.Label; + if (name === 'admin_centre') return RoleType.AdminCentre; + if (name === 'stop') return RoleType.Stop; + return RoleType.Other; +} + +/** Member Options. Relations is skipped as it is not supported / has no use. */ +export type Member = NodeMember | WayMember; +/** Node Member */ +export interface NodeMember { + id: number; + role: RoleType; + node: VectorPoint; +} +/** Way Member */ +export interface WayMember { + id: number; + role: RoleType; + way: VectorLineString; +} + +/** Relation coordinates from ways with information about node relations. */ +export type RelationGeometry = RelationGeometryLines | RelationGeometryArea; +/** Lines Geometry */ +export interface RelationGeometryLines { + type: 0; + coordinates: VectorMultiLineString; + nodes: NodeMember[]; +} +/** Area Geometry */ +export interface RelationGeometryArea { + type: 1; + coordinates: VectorMultiPolygon; + nodes: NodeMember[]; +} + +/** + * + */ +export class Relation { + id = -1; + info?: Info; + // Parallel arrays + #keys: number[] = []; + #vals: number[] = []; + #rolesSid: number[] = []; // This should have been defined as uint32 for consistency, but it is now too late to change it + #memids: number[] = []; // DELTA encoded + #types: MemberType[] = []; + #build?: RelationGeometry; + + /** + * @param primitiveBlock + * @param reader + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + public reader: OSMReader, + pbf?: Protobuf, + ) { + this.primitiveBlock = primitiveBlock; + if (pbf !== undefined) pbf.readMessage(this.#readLayer, this); + } + + /** + * @param options + */ + isFilterable(): boolean { + const { primitiveBlock: pb, reader } = this; + const { tagFilter } = reader; + if (tagFilter !== undefined) { + for (let i = 0; i < this.#keys.length; i++) { + const keyStr = pb.getString(this.#keys[i]); + const valStr = pb.getString(this.#vals[i]); + if (tagFilter.matchFound('Relation', keyStr, valStr)) return false; + } + // if we make it here, we didn't find any matching tags + return true; + } + return false; + } + + /** + * + */ + properties(): Record { + return this.primitiveBlock.tags(this.#keys, this.#vals); + } + + /** + * Each member can be node, way or relation. + * @returns an array of members associated with this relation + */ + members(): Member[] { + const { primitiveBlock: pb, reader } = this; + const res: Member[] = []; + let memid: number = 0; + for (let i = 0; i < this.#memids.length; i++) { + memid += this.#memids[i]; + const role = fromStringToRoleType(pb.getString(this.#rolesSid[i])); + const curType = this.#types[i]; + if (curType === MemberType.Node) { + const node = reader.nodes.get(memid); + if (node !== undefined) res.push({ id: memid, role, node }); + } else if (curType === MemberType.Way) { + const way = reader.ways.get(memid); + if (way !== undefined) res.push({ id: memid, role, way }); + } else { + // Relation -> no-op + } + } + return res; + } + + /** + * + */ + toVectorGeometry(): undefined | RelationGeometry { + if (this.#build !== undefined) return this.#build; + const members = this.members(); + const nodes: NodeMember[] = members.filter((m) => 'node' in m); + this.#build = buildGeometry( + members.filter((m) => 'way' in m), + nodes, + ); + return this.#build; + } + + /** + * Convert members to vector geometry. members are grouped by type if applicable. + * (outer + inner are grouped together to be a polygon/multipolygon for instance) + */ + toVectorFeature(): undefined | VectorFeature { + const vg = this.toVectorGeometry(); + if (vg === undefined) return; + const { type, coordinates } = vg; + const is3D = false; + const geometry: VectorGeometry = + type === 0 + ? coordinates.length === 1 + ? { type: 'LineString', is3D, coordinates: coordinates[0] } + : { type: 'MultiLineString', is3D, coordinates } + : coordinates.length === 1 + ? { type: 'Polygon', is3D, coordinates: coordinates[0] } + : { type: 'MultiPolygon', is3D, coordinates }; + return { + id: this.id, + type: 'VectorFeature', + properties: this.properties(), + geometry, + metadata: this.info?.toBlock(), + }; + } + + /** + * @param tag + * @param relation + * @param pbf + */ + #readLayer(tag: number, relation: Relation, pbf: Protobuf): void { + if (tag === 1) relation.id = pbf.readVarint(); + else if (tag === 2) relation.#keys = pbf.readPackedVarint(); + else if (tag === 3) relation.#vals = pbf.readPackedVarint(); + else if (tag === 4) relation.info = new Info(relation.primitiveBlock, pbf); + else if (tag === 8) relation.#rolesSid = pbf.readPackedVarint(); + else if (tag === 9) relation.#memids = pbf.readPackedSVarint(); + else if (tag === 10) relation.#types = pbf.readPackedVarint(); + else throw new Error(`unexpected tag ${tag}`); + } +} + +/** + * Given a group of Members whose type is "way", build a multilinestring or multipolygon Feature. + * If the ways include an 'outer' or 'inner', then we know its an area, otherwise its a line. + * @param members - an array of way members + * @param nodes + * @returns - a multipolygon + */ +function buildGeometry(members: WayMember[], nodes: NodeMember[]): undefined | RelationGeometry { + // prep variables + const polygons: VectorMultiPolygon = []; + const currentPolygon: VectorPolygon = []; + const currentRing: VectorLineString = []; + + const isArea = + members.some((m) => m.role === RoleType.Outer) || + members.some((m) => m.role === RoleType.Inner); + + // prepare step: members are stored out of order + sortMembers(members); + + for (const member of members) { + // Using "isClockwise", depending on whether the ring is outer or inner, + // we may need to reverse the order of the points. Every time we find the + // first and last point are the same, close out the ring, add it to the current + // polygon, and start a new ring. if the current polygon is NOT empty, we store + // it in the polygons list and start a new one before adding the completed ring. + // NOTE: Due to the nature of OSM data, it is possible that resulting ring is reversed. + // Check against the current ring to see if the way needs to be edited. + // + // grab the geometry from the member + const geometry = member.way; + if (geometry === undefined) return; + // store in current ring, checking current rings order + + if (currentRing.length === 0) { + currentRing.push(...geometry); + } else { + currentRing.push(...geometry.slice(1)); + } + // if current rings first and last point are the same, close out the ring + if (equalPoints(currentRing[0], currentRing[currentRing.length - 1])) { + // add the ring to the current polygon. If member role is outer and + // currentPolygon already has data, we need to store the current poly and + // start a new polygon. + // If the member role is inner, we can add the ring to the + // current polygon. + if (member.role === RoleType.Outer && currentPolygon.length > 0) { + polygons.push(currentPolygon); + } + currentPolygon.push(currentRing); + } + } + + // Last step is to build: + // flush ring if it exists + if (currentRing.length > 0) currentPolygon.push(currentRing); + if (!isArea) return { type: 0, coordinates: currentPolygon, nodes }; + // flush the current polygon if it exists + if (currentPolygon.length > 0) polygons.push(currentPolygon); + // grab the polys and return a feature + return { type: 1, coordinates: polygons, nodes }; +} + +/** + * @param a - the first point + * @param b - the second point + * @returns true if the points are equal + */ +function equalPoints(a: VectorPoint, b: VectorPoint): boolean { + return a.x === b.x && a.y == b.y; +} + +/** + * osm throws relation members out of order, so we need to not only sort them + * but also check if the first and last points of each way follow the same direction. + * @param members - the ways to be sorted + */ +function sortMembers(members: WayMember[]): void { + if (members.length < 3) return; + for (let i = 0; i < members.length - 1; i++) { + const curWay = members[i].way; + const curFirstPoint = curWay[0]; + const curLastPoint = curWay[curWay.length - 1]; + // if current way is already self closing break + if (curFirstPoint == curLastPoint) break; + for (let j = i + 1; j < members.length; j++) { + const nextWay = members[j].way; + const nextFirstPoint = nextWay[0]; + const nextLastPoint = nextWay[nextWay.length - 1]; + // if we find a match between any of the points, swap the member positions + // if curFirstPoint == nextFirstPoint or curLastPoint == nextLastPoint + // swap the order + const equalFirst = equalPoints(curFirstPoint, nextFirstPoint); + const equalLast = equalPoints(curLastPoint, nextLastPoint); + const equalFirstLast = equalPoints(curFirstPoint, nextLastPoint); + const equalLastFirst = equalPoints(curLastPoint, nextFirstPoint); + if (equalFirst || equalLast || equalFirstLast || equalLastFirst) { + if (equalFirst) { + curWay.reverse(); + } else if (equalLast) { + nextWay.reverse(); + } else if (equalFirstLast) { + curWay.reverse(); + nextWay.reverse(); + } + // we want to move the found member to be next to the current member + if (i + 1 != j) { + const temp = members[i + 1]; + members[i + 1] = members[j]; + members[j] = temp; + } + break; + } + } + } +} diff --git a/src/readers/osm/relation.zig b/src/readers/osm/relation.zig deleted file mode 100644 index 28c81e1d..00000000 --- a/src/readers/osm/relation.zig +++ /dev/null @@ -1,273 +0,0 @@ -const std = @import("std"); -const StringHashMap = std.StringHashMap; -const ArrayList = std.ArrayList; -const Protobuf = @import("pbf").Protobuf; -const Info = @import("info.zig").Info; -const Node = @import("node.zig").Node; -const NodeMember = @import("node.zig").NodeMember; -const Way = @import("way.zig").Way; -const WayMember = @import("way.zig").WayMember; -const PrimitiveBlock = @import("primitive.zig").PrimitiveBlock; -const Options = @import("main.zig").Options; -const json = @import("json"); -const Feature = json.Feature; -const S2Feature = json.S2Feature; -const convertGeoJSON = @import("../vectors/convert.zig").convertGeoJSON; -const buildArea = @import("areaBuilder.zig").buildArea; - -pub fn RelationHandle(comptime C: type) type { - return fn (ctx: C, node: *Relation) anyerror!void; -} - -pub const MemberType = enum(u8) { - Node = 0, - Way = 1, - Relation = 2, -}; - -pub const RoleType = enum { - Area, - Outer, - Inner, - From, - To, - Via, - Label, - AdminCentre, - Stop, - Empty, - Other, - const roles = [_][]const u8{ - "area", - "outer", - "inner", - "from", - "to", - "via", - "label", - "admin_centre", - "stop", - }; - const Self = @This(); - pub fn Init(name: []const u8) RoleType { - if (name.len == 0) { - return RoleType.Empty; - } - for (roles, 0..) |v, i| { - if (std.mem.eql(u8, v, name)) { - return @as(RoleType, @enumFromInt(@as(u3, @truncate(i)))); - } - } - return RoleType.Other; - } -}; - -pub const Item = union(enum) { - node: NodeMember, - way: WayMember, - relation: *Relation, -}; - -pub const Member = struct { - role: RoleType, - item: Item, -}; - -pub const MemberFeature = struct { - role: RoleType, - item: Feature, -}; - -pub const MemberS2Feature = struct { - role: RoleType, - item: S2Feature, -}; - -pub const Relation = struct { - id: i64 = -1, - // Parallel arrays. - _keys: []usize = &[_]usize{}, - _vals: []usize = &[_]usize{}, - _info: ?Info = null, - // Parallel arrays - roles_sid: []usize = &[_]usize{}, // This should have been defined as uint32 for consistency, but it is now too late to change it - memids: []i64 = &[_]i64{}, // DELTA encoded - types: []MemberType = &[_]MemberType{}, - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !Relation { - var relation = Relation{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(Relation, &relation, Relation.readLayer); - return relation; - } - - pub fn isFilterable(self: Self, options: *const Options) bool { - if (options.tagFilter != null) { - var tagFilter = options.tagFilter.?; - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - const keyStr = self.primitiveBlock.getString(self._keys[i]); - const valStr = self.primitiveBlock.getString(self._vals[i]); - if (tagFilter.matchFound(.Relation, keyStr, valStr)) return false; - } - // if we make it here, we didn't find any matching tags - return true; - } - return false; - } - - pub fn info(self: Self) ?Info { - if (self._info == null) return null; - self._info.?.primitiveBlock = self.primitiveBlock; - return self._info; - } - - pub fn tags(self: Self, allocator: std.mem.Allocator) !StringHashMap([]const u8) { - return try self.primitiveBlock.tags(self._keys, self._vals, allocator); - } - - pub fn tagsAsJSONString(self: Self, allocator: std.mem.Allocator) !?[]const u8 { - return try self.primitiveBlock.tagsAsJSONString(self._keys, self._vals, allocator); - } - - pub fn hasKey(self: Self, key: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key)) return true; - } - return false; - } - - pub fn hasKeyValue(self: Self, key: []const u8, val: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key) and - std.mem.eql(u8, self.primitiveBlock.getString(self._vals[i]), val)) return true; - } - return false; - } - - /// Each member can be node, way or relation. - pub fn members(self: Self, allocator: std.mem.Allocator) !?[]Member { - var res = try ArrayList(Member).initCapacity(allocator, self.memids.len); - var i: usize = 0; - var memid: i64 = 0; - while (i < self.memids.len) : (i += 1) { - memid += self.memids[i]; - const role = RoleType.Init(self.primitiveBlock.getString(self.roles_sid[i])); - var item: ?Item = null; - const curType = self.types[i]; - switch (curType) { - MemberType.Node => { - const node = try self.primitiveBlock.getNodeMember(memid, allocator); - if (node == null) item = null else item = Item{ .node = node.? }; - }, - MemberType.Way => { - var way = try self.primitiveBlock.getWayMember(memid, allocator); - if (way == null) { - item = null; - } else { - // be sure to update hte primitive block before use - way.?.primitiveBlock = self.primitiveBlock; - item = Item{ .way = way.? }; - } - }, - MemberType.Relation => { - const relation = self.primitiveBlock.relations_.getPtr(memid); - if (relation == null) item = null else item = Item{ .relation = relation.? }; - }, - } - if (item == null) { - // if the relation is missing nodes or ways we just give up, - // but if it's missing a relation we can still return the rest. - if (curType != MemberType.Relation) { - res.deinit(); - return null; - } - } else { - res.appendAssumeCapacity(Member{ .role = role, .item = item.? }); - } - } - return try res.toOwnedSlice(); - } - - /// convert members to geoJSON. members are grouped by type if applicable. - /// (outer + inner are grouped together to be a polygon/multipolygon for instance) - pub fn toGeoJSON(self: Self, arena: *std.heap.ArenaAllocator) !?[]MemberFeature { - const allocator = arena.allocator(); - // pull in members - const mmbrs = try self.members(allocator); - if (mmbrs == null) return null; - var res = ArrayList(MemberFeature).init(allocator); - - // group area members on our way - var areaMembers = ArrayList(*Member).init(allocator); - defer areaMembers.deinit(); - - for (mmbrs.?) |*mmbr| { - switch (mmbr.item) { - .node => |n| try res.append(MemberFeature{ .role = mmbr.role, .item = try n.toGeoJSON(arena) }), - .way => |*w| { - if (mmbr.role == RoleType.Outer or mmbr.role == RoleType.Inner) { - try areaMembers.append(mmbr); - } else { - const feat = try w.toGeoJSON(arena, true); - if (feat != null) try res.append(MemberFeature{ .role = mmbr.role, .item = feat.? }); - } - }, - .relation => |r| { - r.primitiveBlock = self.primitiveBlock; - const geo = try r.toGeoJSON(arena); - if (geo != null) try res.appendSlice(geo.?); - }, - } - } - - // add area object - if (areaMembers.items.len > 0) { - const id: ?u64 = if (self.id >= 0) @as(u64, @intCast(self.id)) else null; - const props = (try self.tagsAsJSONString(allocator)) orelse "{}"; - const properties = try json.Value.FromString(props, arena); - const amSlice = try areaMembers.toOwnedSlice(); - const item = try buildArea(amSlice, id, properties, arena); - if (item == null) return null; - try res.append(MemberFeature{ - .role = RoleType.Area, - .item = item.?, - }); - } - - return try res.toOwnedSlice(); - } - - /// convert members to S2JSON. members are grouped by type if applicable. - /// (outer + inner are grouped together to be a s2polygon/s2multipolygon for instance) - pub fn toS2JSON(self: Self, arena: *std.heap.ArenaAllocator) !?[]MemberS2Feature { - const geojson = try self.toGeoJSON(arena); - if (geojson == null) return null; - var res = try ArrayList(MemberS2Feature).initCapacity(arena.allocator(), geojson.?.len); - - for (geojson.?) |*member| { - const s2jsons = try convertGeoJSON(&member.item, 0.125, arena); - for (s2jsons) |s2j| { - try res.append(MemberS2Feature{ .role = member.role, .item = s2j }); - } - } - - return try res.toOwnedSlice(); - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.id = pbf.readVarint(i64), - 2 => self._keys = try pbf.readPacked(usize), - 3 => self._vals = try pbf.readPacked(usize), - 4 => self._info = try Info.Init(pbf, self.primitiveBlock), - 8 => self.roles_sid = try pbf.readPacked(usize), - 9 => self.memids = try pbf.readPackedS(i64), - 10 => self.types = try pbf.readPacked(MemberType), - else => unreachable, - } - } -}; diff --git a/src/readers/osm/way.ts b/src/readers/osm/way.ts new file mode 100644 index 00000000..093a1920 --- /dev/null +++ b/src/readers/osm/way.ts @@ -0,0 +1,159 @@ +import { Info } from './info'; + +import type { PrimitiveBlock } from './primitive'; +import type { Pbf as Protobuf } from 'open-vector-tile'; +import type { InfoBlock, OSMReader } from '.'; + +import type { VectorFeature, VectorGeometry, VectorLineString } from 's2-tools/geometry'; + +/** + * + */ +export class Way { + id = -1; + info?: Info; + // Parallel arrays + #keys: number[] = []; + #vals: number[] = []; + #refs: number[] = []; // DELTA coded + // Optional infield lat-lon + // NOTE: I'm not going to bother implementing this. + // #lats: number[] = []; // optional DELTA coded + // #lons: number[] = []; // optional DELTA coded + #build?: VectorLineString; + + /** + * @param primitiveBlock + * @param reader + * @param pbf + */ + constructor( + public primitiveBlock: PrimitiveBlock, + public reader: OSMReader, + pbf: Protobuf, + ) { + pbf.readMessage(this.#readLayer, this); + } + + /** + * @param options + */ + isFilterable(): boolean { + const { tagFilter } = this.reader; + if (tagFilter !== undefined) { + for (let i = 0; i < this.#keys.length; i++) { + const keyStr = this.primitiveBlock.getString(this.#keys[i]); + const valStr = this.primitiveBlock.getString(this.#vals[i]); + if (tagFilter.matchFound('Way', keyStr, valStr)) return false; + } + // if we make it here, we didn't find any matching tags + return true; + } + return false; + } + + /** + * + */ + properties(): Record { + return this.primitiveBlock.tags(this.#keys, this.#vals); + } + + /** + * + */ + nodeRefs(): number[] { + const res: number[] = []; + let ref = 0; + for (let i = 0; i < this.#refs.length; i++) { + ref += this.#refs[i]; + res.push(ref); + } + return res; + } + + /** + * @param key + * @param val + */ + hasKeyValue(key: string, val?: string): boolean { + const { primitiveBlock: pb } = this; + for (let i = 0; i < this.#keys.length; i++) { + if (pb.getString(this.#keys[i]) === key) { + if (val === undefined) return true; + if (pb.getString(this.#vals[i]) === val) return true; + } + } + return false; + } + + /** + * @param maybeArea + */ + isArea(): boolean { + const { upgradeWaysToAreas } = this.reader; + if ( + (upgradeWaysToAreas && + this.#refs.length >= 4 && + this.#refs[0] == this.#refs[this.#refs.length - 1]) || + this.hasKeyValue('area', 'yes') + ) { + return true; + } + return false; + } + + /** + * + */ + toVectorGeometry(): undefined | VectorLineString { + if (this.#build !== undefined) return this.#build; + const { reader } = this; + const res: VectorLineString = []; + const nodeRefs = this.nodeRefs(); + for (const ref of nodeRefs) { + const node = reader.nodes.get(ref); + if (node === undefined) return; + res.push({ ...node }); + } + this.#build = res; + return res; + } + + /** + * + */ + toVectorFeature(): undefined | VectorFeature { + const isArea = this.isArea(); + const coordinates = this.toVectorGeometry(); + if (coordinates === undefined) return; + const geometry: VectorGeometry = isArea + ? { type: 'Polygon', is3D: false, coordinates: [coordinates] } + : { type: 'LineString', is3D: false, coordinates }; + return { + id: this.id, + type: 'VectorFeature', + properties: this.properties(), + geometry, + metadata: this.info?.toBlock(), + }; + } + + /** + * @param tag + * @param way + * @param pbf + */ + #readLayer(tag: number, way: Way, pbf: Protobuf): void { + if (tag === 1) way.id = pbf.readVarint(); + else if (tag === 2) way.#keys = pbf.readPackedVarint(); + else if (tag === 3) way.#vals = pbf.readPackedVarint(); + else if (tag === 4) way.info = new Info(way.primitiveBlock, pbf); + else if (tag === 8) way.#refs = pbf.readPackedSVarint(); + // skip, not used. + else if (tag === 9 || tag === 10) return; + // else if (tag === 9) way.#lats = pbf.readPackedSVarint(); + // else if (tag === 10) way.#lons = pbf.readPackedSVarint(); + else throw new Error(`Unknown tag: ${tag}`); + } +} diff --git a/src/readers/osm/way.zig b/src/readers/osm/way.zig deleted file mode 100644 index 428b00a6..00000000 --- a/src/readers/osm/way.zig +++ /dev/null @@ -1,249 +0,0 @@ -const std = @import("std"); -const StringHashMap = std.StringHashMap; -const Protobuf = @import("pbf").Protobuf; -const ArrayList = std.ArrayList; -const Info = @import("info.zig").Info; -const Node = @import("node.zig").Node; -const PrimitiveBlock = @import("primitive.zig").PrimitiveBlock; -const Options = @import("main.zig").Options; -const json = @import("json"); -const Feature = json.Feature; -const S2Feature = json.S2Feature; -const convertGeoJSON = @import("../vectors/convert.zig").convertGeoJSON; - -pub fn WayHandle(comptime C: type) type { - return fn (ctx: C, node: *Way) anyerror!void; -} - -pub const WayMember = struct { - id: u64 = 0, - _refs: []i64, - propStr: ?[]const u8 = null, - primitiveBlock: *PrimitiveBlock = undefined, - const Self = @This(); - - pub fn Init(id: i64, refs: []i64, propStr: ?[]const u8) WayMember { - return WayMember{ - .id = @as(u64, @intCast(id)), - ._refs = refs, - .propStr = propStr, - }; - } - - pub fn nodeRefs(self: Self, allocator: std.mem.Allocator) ![]u64 { - var res = try ArrayList(u64).initCapacity(allocator, self._refs.len); - var i: usize = 0; - var ref: i64 = 0; - while (i < self._refs.len) : (i += 1) { - ref += self._refs[i]; - res.appendAssumeCapacity(@as(u64, @intCast(ref))); - } - return try res.toOwnedSlice(); - } - - pub fn nodes(self: Self, allocator: std.mem.Allocator) !?json.LineString { - var res = try ArrayList([]f64).initCapacity(allocator, self._refs.len); - var i: usize = 0; - var ref: i64 = 0; - while (i < self._refs.len) : (i += 1) { - ref += self._refs[i]; - const node = try self.primitiveBlock.getNode(ref); - if (node == null) return null; - var point = try allocator.alloc(f64, 2); - point[0] = node.?.lon; - point[1] = node.?.lat; - res.appendAssumeCapacity(point); - } - return try res.toOwnedSlice(); - } - - pub fn isArea(self: Self, maybeArea: bool) bool { - if (maybeArea and self._refs.len >= 4 and - self._refs[0] == self._refs[self._refs.len - 1]) - { - return true; - } - return false; - } - - pub fn geometry(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool, areaYes: bool) !?json.Geometry { - const allocator = arena.allocator(); - const nodes_ = try self.nodes(allocator); - if (nodes_ == null) return null; - if (areaYes or self.isArea(maybeArea)) { - var poly = try allocator.alloc([][]f64, 1); - poly[0] = nodes_.?; - return json.Geometry{ .polygon = poly }; - } - return json.Geometry{ .lineString = nodes_.? }; - } - - pub fn toGeoJSON(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool) !?Feature { - var props = try json.Value.FromString(self.propStr orelse "{}", arena); - const geo = try self.geometry(arena, maybeArea, props.containsPair("area", "yes")); - if (geo == null) return null; - - return Feature{ - .id = self.id, - .geometry = geo.?, - .properties = props, - }; - } - - pub fn toS2JSON(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool) !?[]S2Feature { - var geojson = try self.toGeoJSON(arena, maybeArea); - if (geojson == null) return null; - - return try convertGeoJSON(&(geojson.?), 0.125, arena); - } -}; - -pub const Way = struct { - id: i64 = -1, - _info: ?Info = null, - // Parallel arrays - _keys: []usize = &[_]usize{}, - _vals: []usize = &[_]usize{}, - _refs: []i64 = &[_]i64{}, // DELTA coded - // Optional infield lat-lon - // NOTE: I'm not going to bother implementing this. - _lats: ?[]i64 = null, // optional DELTA coded - _lons: ?[]i64 = null, // optional DELTA coded - primitiveBlock: *PrimitiveBlock, - const Self = @This(); - - pub fn Init(pbf: *Protobuf, primitiveBlock_: *PrimitiveBlock) !Way { - var way = Way{ .primitiveBlock = primitiveBlock_ }; - try pbf.readMessage(Way, &way, Way.readLayer); - return way; - } - - pub fn isFilterable(self: Self, options: *const Options) bool { - if (options.tagFilter != null) { - var tagFilter = options.tagFilter.?; - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - const keyStr = self.primitiveBlock.getString(self._keys[i]); - const valStr = self.primitiveBlock.getString(self._vals[i]); - if (tagFilter.matchFound(.Way, keyStr, valStr)) return false; - } - // if we make it here, we didn't find any matching tags - return true; - } - return false; - } - - pub fn info(self: Self) ?Info { - if (self._info == null) return null; - self._info.?.primitiveBlock = self.primitiveBlock; - return self._info; - } - - pub fn tags(self: Self, allocator: std.mem.Allocator) !StringHashMap([]const u8) { - return try self.primitiveBlock.tags(self._keys, self._vals, allocator); - } - - pub fn tagsAsJSONString(self: Self, allocator: std.mem.Allocator) !?[]const u8 { - return try self.primitiveBlock.tagsAsJSONString(self._keys, self._vals, allocator); - } - - pub fn hasKey(self: Self, key: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key)) return true; - } - return false; - } - - pub fn hasKeyValue(self: Self, key: []const u8, val: []const u8) bool { - var i: usize = 0; - while (i < self._keys.len) : (i += 1) { - if (std.mem.eql(u8, self.primitiveBlock.getString(self._keys[i]), key) and - std.mem.eql(u8, self.primitiveBlock.getString(self._vals[i]), val)) return true; - } - return false; - } - - pub fn nodeRefs(self: Self, allocator: std.mem.Allocator) ![]u64 { - var res = try ArrayList(u64).initCapacity(allocator, self._refs.len); - var i: usize = 0; - var ref: i64 = 0; - while (i < self._refs.len) : (i += 1) { - ref += self._refs[i]; - res.appendAssumeCapacity(@as(u64, @intCast(ref))); - } - return try res.toOwnedSlice(); - } - - pub fn nodes(self: Self, allocator: std.mem.Allocator) !?json.LineString { - var res = try ArrayList([]f64).initCapacity(allocator, self._refs.len); - var i: usize = 0; - var ref: i64 = 0; - while (i < self._refs.len) : (i += 1) { - ref += self._refs[i]; - const node = try self.primitiveBlock.getNode(ref); - if (node == null) return null; - var point = try allocator.alloc(f64, 2); - point[0] = node.?.lon; - point[1] = node.?.lat; - res.appendAssumeCapacity(point); - } - return try res.toOwnedSlice(); - } - - pub fn isArea(self: Self, maybeArea: bool) bool { - if ((maybeArea and self._refs.len >= 4 and - self._refs[0] == self._refs[self._refs.len - 1]) or - self.hasKeyValue("area", "yes")) - { - return true; - } - return false; - } - - pub fn geometry(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool) !?json.Geometry { - const allocator = arena.allocator(); - const nodes_ = try self.nodes(allocator); - if (nodes_ == null) return null; - if (self.isArea(maybeArea)) { - var poly = try allocator.alloc([][]f64, 1); - poly[0] = nodes_.?; - return json.Geometry{ .polygon = poly }; - } - return json.Geometry{ .lineString = nodes_.? }; - } - - pub fn toGeoJSON(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool) !?Feature { - const allocator = arena.allocator(); - const id: ?u64 = if (self.id >= 0) @as(u64, @intCast(self.id)) else null; - const props = (try self.tagsAsJSONString(allocator)) orelse "{}"; - const geo = try self.geometry(arena, maybeArea); - if (geo == null) return null; - - return Feature{ - .id = id, - .geometry = geo.?, - .properties = try json.Value.FromString(props, arena), - }; - } - - pub fn toS2JSON(self: Self, arena: *std.heap.ArenaAllocator, maybeArea: bool) !?[]S2Feature { - var geojson = try self.toGeoJSON(arena, maybeArea); - if (geojson == null) return null; - - return try convertGeoJSON(&(geojson.?), 0.125, arena); - } - - fn readLayer(self: *Self, tag: u64, pbf: *Protobuf) !void { - switch (tag) { - 1 => self.id = pbf.readVarint(i64), - 2 => self._keys = try pbf.readPacked(usize), - 3 => self._vals = try pbf.readPacked(usize), - 4 => self._info = try Info.Init(pbf, self.primitiveBlock), - 8 => self._refs = try pbf.readPackedS(i64), - 9 => self._lats = try pbf.readPackedS(i64), - 10 => self._lons = try pbf.readPackedS(i64), - else => unreachable, - } - } -}; diff --git a/src/readers/shapefile/dbf.ts b/src/readers/shapefile/dbf.ts index e38bac52..a7c9221e 100644 --- a/src/readers/shapefile/dbf.ts +++ b/src/readers/shapefile/dbf.ts @@ -29,7 +29,6 @@ export interface DBFRow { export default class DataBaseFile { #header!: DBFHeader; #rows: DBFRow[]; - textDecoder: TextDecoder; /** * @param reader - the input data structure to parse @@ -37,10 +36,9 @@ export default class DataBaseFile { */ constructor( public reader: Reader, - encoding = 'utf-8', + encoding?: string, ) { - // @ts-expect-error - linting bug - this.textDecoder = new TextDecoder(encoding); + if (encoding !== undefined) reader.setStringEncoding(encoding); this.#parseHeader(); this.#rows = this.#parseRowHeader(); } @@ -104,7 +102,7 @@ export default class DataBaseFile { let offset = 32; while (offset < len) { res.push({ - name: this.#decode(reader.slice(offset, offset + 11)), + name: reader.parseString(offset, 11), dataType: String.fromCharCode(reader.getUint8(offset + 11)), len: reader.getUint8(offset + 16), decimal: reader.getUint8(offset + 17), @@ -143,9 +141,8 @@ export default class DataBaseFile { */ #parseValue(offset: number, len: number, type: string) { const { reader } = this; - const data = reader.slice(offset, offset + len); + const textData = reader.parseString(offset, len); - const textData = this.#decode(data); switch (type) { case 'N': case 'F': @@ -154,7 +151,7 @@ export default class DataBaseFile { case 'D': return new Date( parseFloat(textData.slice(0, 4)), - parseInt(textData.slice(4, 6), 10) - 1, + parseInt(textData.slice(4, 6)) - 1, parseFloat(textData.slice(6, 8)), ).getUTCDate(); case 'L': @@ -163,17 +160,4 @@ export default class DataBaseFile { return textData; } } - - /** - * @param data - a Uint8Array input - * @returns - a string - */ - #decode(data: DataView): string { - const { textDecoder } = this; - const out = - textDecoder.decode(data, { - stream: true, - }) + textDecoder.decode(); - return out.replace(/\0/g, '').trim(); - } } diff --git a/src/readers/shapefile/file.ts b/src/readers/shapefile/file.ts index 8c0750dd..1a4862a5 100644 --- a/src/readers/shapefile/file.ts +++ b/src/readers/shapefile/file.ts @@ -1,13 +1,9 @@ -// import fetcher from './fetch'; - import DataBaseFile from './dbf'; import FileReader from '../fileReader'; import Shapefile from './shp'; +import { Transformer } from 's2-tools/proj4'; import { exists, readFile } from 'fs/promises'; -// import proj4 from 'proj4'; -// import unzip from './unzip'; - export * from './dbf'; export * from './shp'; @@ -57,10 +53,11 @@ export async function fromPath(input: string) { * @returns - a Shapefile */ export async function fromDefinition(def: Definition): Promise { - const { shp, dbf, cpg } = def; // TODO: prj + const { shp, dbf, prj, cpg } = def; const encoding = cpg ? await readFile(cpg, { encoding: 'utf8' }) : 'utf8'; + const transform = prj ? new Transformer(await readFile(prj, { encoding: 'utf8' })) : undefined; const dbfReader = dbf !== undefined ? new FileReader(dbf) : undefined; const databaseFile = dbfReader !== undefined ? new DataBaseFile(dbfReader, encoding) : undefined; - // TODO: Projection - return new Shapefile(new FileReader(shp), undefined, databaseFile); + + return new Shapefile(new FileReader(shp), databaseFile, transform); } diff --git a/src/readers/shapefile/index.ts b/src/readers/shapefile/index.ts index 2ac9bedf..0f19ab15 100644 --- a/src/readers/shapefile/index.ts +++ b/src/readers/shapefile/index.ts @@ -1,8 +1,7 @@ import { BufferReader } from '..'; import DataBaseFile from './dbf'; import Shapefile from './shp'; - -// import proj4 from 'proj4'; +import { Transformer } from 's2-tools/proj4'; export * from './dbf'; export * from './shp'; diff --git a/src/readers/shapefile/mmap.ts b/src/readers/shapefile/mmap.ts new file mode 100644 index 00000000..7e7be642 --- /dev/null +++ b/src/readers/shapefile/mmap.ts @@ -0,0 +1,63 @@ +import DataBaseFile from './dbf'; +import MMapReader from '../mmapReader'; +import Shapefile from './shp'; +import { Transformer } from 's2-tools/proj4'; +import { exists, readFile } from 'fs/promises'; + +export * from './dbf'; +export * from './shp'; + +/** A description of what relevant files exist and where */ +export interface Definition { + /** The path to the .shp file */ + shp: string; + /** The path to the .dbf file. dbf is optional, but needed if you want attributes */ + dbf?: string; + /** + * The path to the .prj file. prj is optional, but needed if your file is in some + * projection you don't want it in + */ + prj?: string; + /** + * The path to the .cpg file. cpg is optional, but needed if your dbf is in some + * weird (non utf8) encoding. + */ + cpg?: string; +} + +/** + * Assumes the input is pointing to a shapefile or name without the extension. + * The algorithm will find the rest of the paths if they exist. + * @param input - the path to the .shp file or name without the extension + * @returns - a Shapefile + */ +export async function fromPath(input: string) { + const path = input.replace('.shp', ''); + const shp = `${path}.shp`; + const dbf = `${path}.dbf`; + const prj = `${path}.prj`; + const cpg = `${path}.cpg`; + if (!(await exists(shp))) throw new Error('Shapefile does not exist'); + const definition: Definition = { + shp, + dbf: (await exists(dbf)) ? dbf : undefined, + prj: (await exists(prj)) ? prj : undefined, + cpg: (await exists(cpg)) ? cpg : undefined, + }; + return fromDefinition(definition); +} + +/** + * Build a Shapefile from a Definition + * @param def - a description of the data to parse + * @returns - a Shapefile + */ +export async function fromDefinition(def: Definition): Promise { + const { shp, dbf, prj, cpg } = def; + const encoding = cpg ? await readFile(cpg, { encoding: 'utf8' }) : 'utf8'; + const transform = prj ? new Transformer(await readFile(prj, { encoding: 'utf8' })) : undefined; + const dbfReader = dbf !== undefined ? new MMapReader(dbf) : undefined; + const databaseFile = dbfReader !== undefined ? new DataBaseFile(dbfReader, encoding) : undefined; + + return new Shapefile(new MMapReader(shp), databaseFile, transform); +} diff --git a/src/readers/shapefile/shp.ts b/src/readers/shapefile/shp.ts index cc201a1d..59f46bd1 100644 --- a/src/readers/shapefile/shp.ts +++ b/src/readers/shapefile/shp.ts @@ -2,8 +2,8 @@ import { extendBBox } from 's2-tools/geometry'; import type DataBaseFile from './dbf'; -import type { ProjectionTransform } from 's2-tools/proj4/projections'; import type { Reader } from '..'; +import type { Transformer } from 's2-tools/proj4'; import type { BBOX, BBox3D, @@ -40,7 +40,7 @@ export interface SHPRow { /** The Shapefile Reader */ export default class Shapefile { #header!: SHPHeader; - rows: SHPRow[] = []; + rows: number[] = []; /** * @param reader - the input data structure to parse * @param dbf - the dbf file @@ -49,7 +49,7 @@ export default class Shapefile { constructor( public reader: Reader, public dbf?: DataBaseFile, - public transform?: ProjectionTransform, + public transform?: Transformer, ) { this.#parseHeader(); this.#getRows(); @@ -86,8 +86,8 @@ export default class Shapefile { * @yields {VectorFeature} */ *iterate(): IterableIterator { - for (const row of this.rows) { - const feature = this.#parseRow(row); + for (let i = 0; i < this.rows.length; i++) { + const feature = this.#parseRow(this.rows[i], i); if (feature !== undefined) yield feature; } } @@ -113,20 +113,17 @@ export default class Shapefile { } } - /** Internal parser to build all the row data */ + /** Internal parser to build all the row offsets */ #getRows() { + const { reader, rows } = this; let offset = 100; - const len = this.reader.byteLength - 8; + const len = reader.byteLength - 8; while (offset <= len) { - const current = this.#getRow(offset); - if (current === undefined) { - break; - } - offset += 8; - offset += current.len; - if (current.type !== 0) { - this.rows.push(current); - } + const offsetLength = reader.getInt32(offset + 4) << 1; + const type = reader.getInt32(offset + 8, true); + if (offsetLength === 0) break; + if (type !== 0) rows.push(offset); + offset += 8 + offsetLength; } } @@ -138,12 +135,7 @@ export default class Shapefile { const { reader } = this; const id = reader.getInt32(offset); const len = reader.getInt32(offset + 4) << 1; - if (len === 0) { - return { id, len: 0, data: new DataView(new ArrayBuffer(0)), type: 0 }; - } - if (offset + len + 8 > reader.byteLength) { - return; - } + if (len === 0 || offset + len + 8 > reader.byteLength) return; return { id, len, @@ -153,10 +145,13 @@ export default class Shapefile { } /** - * @param row - the row to parse + * @param rowOffset - the row to get and parse + * @param index - the index of the feature * @returns - the parsed feature */ - #parseRow(row: SHPRow): VectorFeature | undefined { + #parseRow(rowOffset: number, index: number): VectorFeature | undefined { + const row = this.#getRow(rowOffset); + if (row === undefined) return; const { id, type, data } = row; const geometry = this.#parseGeometry(type, data); if (geometry === undefined) return; @@ -164,7 +159,7 @@ export default class Shapefile { return { id, type: 'VectorFeature', - properties: this.dbf?.getProperties(id - 1) ?? {}, + properties: this.dbf?.getProperties(index) ?? {}, geometry, }; } @@ -202,7 +197,7 @@ export default class Shapefile { y: data.getFloat64(offset + 8, true), z: offset3D ? data.getFloat64(offset3D, true) : undefined, }; - return this.transform?.inverse(point) ?? point; + return this.transform?.forward(point) ?? point; } /** @@ -288,7 +283,6 @@ export default class Shapefile { // build coordinates let index = 0; const coordinates: VectorMultiLineString = []; - // for (const part of parts) { for (let i = 0; i < numParts; i++) { const partEnd = parts[i + 1] ?? numPoints; // build a line for part diff --git a/src/readers/wkt/geometry.ts b/src/readers/wkt/geometry.ts new file mode 100644 index 00000000..d0e478e7 --- /dev/null +++ b/src/readers/wkt/geometry.ts @@ -0,0 +1,172 @@ +import { cleanString } from '.'; + +import type { + VectorGeometry, + VectorLineString, + VectorMultiLineString, + VectorMultiPolygon, + VectorPoint, + VectorPointGeometry, +} from 's2-tools/geometry'; + +/** + * + */ +export type WKTAValue = VectorPoint | WKTAValue[]; +/** + * + */ +export type WKTArray = WKTAValue[]; + +/** + * @param wktStr + */ +export function parseWKTGeometry(wktStr: string): VectorGeometry { + if (wktStr.startsWith('POINT')) return parseWKTPoint(wktStr, wktStr.startsWith('POINT Z')); + else if (wktStr.startsWith('MULTIPOINT')) + return parseWKTLine(wktStr, 'MultiPoint', wktStr.startsWith('MULTIPOINT Z')); + else if (wktStr.startsWith('LINESTRING')) + return parseWKTLine(wktStr, 'LineString', wktStr.startsWith('LINESTRING Z')); + else if (wktStr.startsWith('MULTILINESTRING')) + return parseWKTMultiLine(wktStr, 'MultiLineString', wktStr.startsWith('MULTILINESTRING Z')); + else if (wktStr.startsWith('POLYGON')) + return parseWKTMultiLine(wktStr, 'Polygon', wktStr.startsWith('POLYGON Z')); + else if (wktStr.startsWith('MULTIPOLYGON')) + return parseWKTMultiPolygon(wktStr, wktStr.startsWith('MULTIPOLYGON Z')); + throw new Error('Unimplemented WKT geometry: ' + wktStr); +} + +/** + * @param wktStr + * @param is3D + */ +function parseWKTPoint(wktStr: string, is3D: boolean): VectorPointGeometry { + const geo = parseWKTArray(wktStr); + return { + type: 'Point', + is3D, + coordinates: geo[0] as VectorPoint, + }; +} + +/** + * @param wktStr + * @param type + * @param is3D + */ +function parseWKTLine( + wktStr: string, + type: 'MultiPoint' | 'LineString', + is3D: boolean, +): VectorGeometry { + let geo = parseWKTArray(wktStr); + geo = + geo.length > 0 && Array.isArray(geo[0]) + ? geo.map((e) => (e as [VectorPoint])[0] as VectorPoint) + : geo; + return { + type, + is3D, + coordinates: geo as VectorLineString, + }; +} + +/** + * @param wktStr + * @param type + * @param is3D + */ +function parseWKTMultiLine( + wktStr: string, + type: 'MultiLineString' | 'Polygon', + is3D: boolean, +): VectorGeometry { + let geo = parseWKTArray(wktStr) as VectorMultiLineString; + geo = + geo.length > 0 && geo[0].length > 0 && Array.isArray(geo[0][0]) + ? geo.map((e) => { + return e.map((e2) => (e2 as unknown as [VectorPoint])[0] as VectorPoint); + }) + : geo; + return { + type, + is3D, + coordinates: geo as VectorMultiLineString, + }; +} + +/** + * @param wktStr + * @param is3D + */ +function parseWKTMultiPolygon(wktStr: string, is3D: boolean): VectorGeometry { + let geo = parseWKTArray(wktStr) as VectorMultiPolygon; + geo = + geo.length > 0 && geo[0].length > 0 && geo[0][0].length > 0 && Array.isArray(geo[0][0][0]) + ? geo.map((e) => { + return e.map((e2) => e2.map((e3) => (e3 as unknown as [VectorPoint])[0] as VectorPoint)); + }) + : geo; + return { + type: 'MultiPolygon', + is3D, + coordinates: geo as VectorMultiPolygon, + }; +} + +/** + * @param wktStr + */ +function parseWKTArray(wktStr: string): WKTArray { + const res: WKTArray = []; + _parseWKTArray(wktStr, res); + return res.length ? (res[0] as WKTArray) : res; +} + +// always return the endBracketIndex if we hit it +/** + * @param wktStr + * @param res + * @param startChar + * @param endChar + */ +function _parseWKTArray(wktStr: string, res: WKTArray): string { + // first get the array name and build the residual + while (wktStr.length) { + let commaIndex = wktStr.indexOf(','); + let startBracketIndex = wktStr.indexOf('('); + const endBracketIndex = wktStr.indexOf(')'); + if (commaIndex === -1) commaIndex = Infinity; + if (startBracketIndex === -1) startBracketIndex = Infinity; + if (commaIndex < Math.min(startBracketIndex, endBracketIndex)) { + // store the value + const key = wktStr.slice(0, commaIndex); + if (key.length > 0) res.push(buildPoint(key)); + wktStr = wktStr.slice(commaIndex + 1); + } else if (startBracketIndex < endBracketIndex) { + // store the array + const array: WKTArray = []; + wktStr = _parseWKTArray(wktStr.slice(startBracketIndex + 1), array); + res.push(array); + } else { + // store the LAST value if it exists, be sure to increment past the bracket for this recursive call + if (endBracketIndex > 0) { + res.push(buildPoint(wktStr.slice(0, endBracketIndex))); + wktStr = wktStr.slice(endBracketIndex + 1); + } else { + wktStr = wktStr.slice(1); + } + return wktStr; + } + } + // hit the end + return wktStr; +} + +/** + * @param str + */ +function buildPoint(str: string): VectorPoint { + const [x, y, z] = cleanString(str).split(' '); + return { x: +x, y: +y, z: z ? +z : undefined }; +} diff --git a/src/readers/wkt/index.ts b/src/readers/wkt/index.ts new file mode 100644 index 00000000..3223abbd --- /dev/null +++ b/src/readers/wkt/index.ts @@ -0,0 +1,15 @@ +export * from './geometry'; +export * from './object'; +export * from './projection'; + +export type * from './projection'; + +/** + * @param str + */ +export function cleanString(str: string): string { + return str + .trim() // Remove whitespace at the start and end + .replace(/^['"]|['"]$/g, '') // Remove single or double quotes from start and end + .replace(/\s+/g, ' '); // Replace multiple spaces with a single space +} diff --git a/src/readers/wkt/object.ts b/src/readers/wkt/object.ts new file mode 100644 index 00000000..2fff7f48 --- /dev/null +++ b/src/readers/wkt/object.ts @@ -0,0 +1,59 @@ +import { cleanString } from '.'; + +/** + * + */ +export type WKTValue = string | WKTValue[]; +/** + * + */ +export type WKTObject = WKTValue[]; + +/** + * @param wktStr + */ +export function parseWKTObject(wktStr: string): WKTObject { + const res: WKTObject = []; + _parseWKTObject(wktStr, res); + return res; +} + +// always return the endBracketIndex if we hit it +/** + * @param wktStr + * @param res + */ +function _parseWKTObject(wktStr: string, res: WKTObject): string { + // first get the object name and build the residual + while (wktStr.length) { + let commaIndex = wktStr.indexOf(','); + let startBracketIndex = wktStr.indexOf('['); + const endBracketIndex = wktStr.indexOf(']'); + if (commaIndex === -1) commaIndex = Infinity; + if (startBracketIndex === -1) startBracketIndex = Infinity; + if (commaIndex < Math.min(startBracketIndex, endBracketIndex)) { + // store the value + const key = wktStr.slice(0, commaIndex); + if (key.length > 0) res.push(cleanString(key)); + wktStr = wktStr.slice(commaIndex + 1); + } else if (startBracketIndex < endBracketIndex) { + // store the object + const key = wktStr.slice(0, startBracketIndex); + const arr: WKTObject = []; + wktStr = _parseWKTObject(wktStr.slice(startBracketIndex + 1), arr); + res.push(cleanString(key), arr); + } else { + // store the LAST value if it exists, be sure to increment past the bracket for this recursive call + if (endBracketIndex > 0) { + const key = cleanString(wktStr.slice(0, endBracketIndex)); + if (key.length > 0) res.push(key); + wktStr = wktStr.slice(endBracketIndex + 1); + } else { + wktStr = wktStr.slice(1); + } + return wktStr; + } + } + // hit the end + return wktStr; +} diff --git a/src/readers/wkt/projection.ts b/src/readers/wkt/projection.ts new file mode 100644 index 00000000..17d20956 --- /dev/null +++ b/src/readers/wkt/projection.ts @@ -0,0 +1,493 @@ +import { degToRad } from '../../geometry'; +import { parseWKTObject } from '.'; + +import type { WKTObject, WKTValue } from '.'; + +/** + * + */ +export interface Authority { + EPSG: string; +} + +/** + * + */ +export interface Unit { + name: string; + convert: number; + AUTHORITY: Authority; +} + +/** + * + */ +export interface Spheroid { + name: string; + a: number; + rf: number; + AUTHORITY: Authority; +} + +/** + * + */ +export interface Datum { + name: string; + SPHEROID?: Spheroid; + AUTHORITY: Authority; + TOWGS84?: DatumParams; +} + +/** + * + */ +export interface GeoGCS { + name: string; + DATUM?: Datum; + PRIMEM?: Unit; + UNIT?: Unit; + AUTHORITY: Authority; +} + +/** + * + */ +export interface VertCS { + name?: string; + VERT_DATUM?: Unit; + UNIT?: Unit; + AXIS?: [string, string][]; + AUTHORITY?: Authority; +} + +/** + * + */ +export type DatumParams = [number, number, number, number, number, number, number]; + +/** + * + */ +export interface WKTCRS { + type?: string; + name?: string; + local?: boolean; + GEOGCS?: GeoGCS; + DATUM?: Datum; + PRIMEM?: Unit; + UNIT?: Unit; + PROJCS?: Omit; + VERT_CS?: VertCS; + PROJECTION?: string; + standard_parallel_1?: number; + standard_parallel_2?: number; + latitude_of_origin?: number; + latitude_of_center?: number; + longitude_of_center?: number; + central_meridian?: number; + false_easting?: number; + false_northing?: number; + AUTHORITY?: Authority; + AXIS?: [string, string][]; + projName?: string; + units?: string; + to_meter?: number; + datumCode?: string; + ellps?: string; + a?: number; + b?: number; + rf?: number; + x0?: number; + y0?: number; + k0?: number; + lat_ts?: number; + longc?: number; + long0?: number; + lat0?: number; + lat1?: number; + lat2?: number; + axis?: string; + srsCode: string; + datum_params?: DatumParams; + scale_factor?: number; + sphere?: boolean; + azimuth?: number; + alpha?: number; +} + +export const KEYWORDS = [ + 'PROJCS', // projected coordinate system + 'GEOGCS', // geographic coordinate system + 'COMPD_CS', // compound coordinate system + 'VERT_CS', // vertical coordinate system + 'PROJECTEDCRS', // projected coordinate system + 'PROJCRS', // projected coordinate system + 'GEOCCS', // geographic coordinate system + 'LOCAL_CS', // local coordinate system + 'GEODCRS', // geographic coordinate system + 'GEODETICCRS', // geographic coordinate system + 'GEODETICDATUM', // geographic coordinate system + 'EDATUM', // geographic coordinate system + 'ENGINEERINGDATUM', // engineering datum + 'VERTCRS', // vertical coordinate system + 'VERTICALCRS', // vertical coordinate system + 'COMPOUNDCRS', // compound coordinate system + 'ENGINEERINGCRS', // engineering coordinate system + 'ENGCRS', // engineering coordinate system + 'FITTED_CS', // fitted coordinate system + 'LOCAL_DATUM', // local datum + 'DATUM', // datum +]; + +/** + * @param srsCode - WKT string + * @returns - true if it is a WKT projection string + */ +export function isWKTProjection(srsCode: string): boolean { + // has words with KEYWORDS in it + return KEYWORDS.some((key) => srsCode.includes(key)); +} + +/** + * @param input + * @param srsCode + */ +export function parseWKTProjection(srsCode: string): WKTCRS { + const obj = parseWKTObject(srsCode); + const res: Record = {}; + parseProj(obj, res); + updateProj(res as unknown as WKTCRS); + return res as unknown as WKTCRS; +} + +/** + * @param obj + * @param res + */ +function parseProj(obj: WKTObject, res: Record): void { + // grab type + const type = obj.shift(); + if (typeof type !== 'string') return; + res.type = type; + obj = obj[0] as WKTObject; + // grab name + const name = obj.shift(); + if (typeof name !== 'string') return; + res.name = res.srsCode = name; + // parse keywords + buildKeywords(obj, res); +} + +/** + * @param obj + * @param res + */ +function buildKeywords(obj: WKTObject, res: Record): void { + while (obj.length > 0) { + const key = obj.shift(); + if (typeof key !== 'string') return; + if (KEYWORDS.includes(key)) { + const value = obj.shift(); + if (Array.isArray(value)) res[key] = buildSubKeywords(value); + } else if (key === 'SPHEROID' || key === 'ELLIPSOID') { + const value = obj.shift(); + if (Array.isArray(value)) res[key] = buildSpheroid(value); + } else if (key === 'UNIT' || key === 'PRIMEM' || key === 'VERT_DATUM') { + const value = obj.shift(); + if (Array.isArray(value)) res[key] = buildUnit(value); + } else if (key === 'AUTHORITY') { + const value = obj.shift(); + if (Array.isArray(value)) res.AUTHORITY = buildAuthority(value); + } else if (key === 'TOWGS84') { + const value = obj.shift(); + if (Array.isArray(value)) res.TOWGS84 = buildTOWGS84(value); + } else if (key === 'PROJECTION') { + const value = obj.shift(); + if (Array.isArray(value) && typeof value[0] === 'string') + res.PROJECTION = res.projName = value[0]; + } else if (key === 'PARAMETER') { + const param = obj.shift(); + if (!Array.isArray(param)) continue; + const [resKey, resValue] = param; + if (typeof resKey === 'string' && typeof resValue === 'string') { + res[resKey] = parseFloat(resValue); + } + } else if (key === 'AXIS') { + const axis = obj.shift(); + if (Array.isArray(axis)) { + if (res.AXIS === undefined) res.AXIS = []; + (res.AXIS as [string, string][]).push(axis as [string, string]); + } + } + } +} + +/** + * @param obj + * @param res + */ +function buildSubKeywords(obj: WKTObject): GeoGCS { + const [name, ...keywords] = obj; + + const res = { + name: name as string, + }; + buildKeywords(keywords, res); + return res as GeoGCS; +} + +/** + * @param input + */ +function buildUnit(input: WKTObject): Unit { + const [name, convert, _authStr, authority] = input; + return { + name: name as string, + convert: parseFloat(convert as string), + AUTHORITY: buildAuthority(authority), + }; +} + +/** + * @param input + */ +function buildSpheroid(input: WKTObject): Spheroid { + const [name, a, rf, _authStr, authority] = input; + return { + name: name as string, + a: parseFloat(a as string), + rf: parseFloat(rf as string), + AUTHORITY: buildAuthority(authority), + }; +} + +/** + * @param input + */ +function buildAuthority(input?: WKTValue): Authority { + return { EPSG: (input ? input[1] : '') as string }; +} + +/** + * @param input + */ +function buildTOWGS84(input: WKTObject): DatumParams { + return input.map((key) => parseFloat(key as string)) as DatumParams; +} + +/** + * @param prj + * @param wkt + */ +function updateProj(wkt: WKTCRS): void { + // adjust projName if necessary + if (wkt.type === 'GEOGCS') { + wkt.projName = 'longlat'; + } else if (wkt.type === 'LOCAL_CS') { + wkt.projName = 'identity'; + wkt.local = true; + } + // improve axis definitions + if (wkt.AXIS) { + let axisOrder = ''; + for (let i = 0, ii = wkt.AXIS.length; i < ii; ++i) { + const axis = [wkt.AXIS[i][0].toLowerCase(), wkt.AXIS[i][1].toLowerCase()]; + if ( + axis[0].indexOf('north') !== -1 || + ((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'north') + ) { + axisOrder += 'n'; + } else if ( + axis[0].indexOf('south') !== -1 || + ((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'south') + ) { + axisOrder += 's'; + } else if ( + axis[0].indexOf('east') !== -1 || + ((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'east') + ) { + axisOrder += 'e'; + } else if ( + axis[0].indexOf('west') !== -1 || + ((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'west') + ) { + axisOrder += 'w'; + } + } + if (axisOrder.length === 2) { + axisOrder += 'u'; + } + if (axisOrder.length === 3) { + wkt.axis = axisOrder; + } + } + // unit adjustments + if (wkt.UNIT) { + wkt.units = wkt.UNIT.name?.toLowerCase(); + if (wkt.units === 'metre') wkt.units = 'meter'; + if (wkt.UNIT.convert !== undefined) { + if (wkt.type === 'GEOGCS') { + if (wkt.DATUM && wkt.DATUM.SPHEROID) { + wkt.to_meter = wkt.UNIT.convert * wkt.DATUM.SPHEROID.a; + } + } else { + wkt.to_meter = wkt.UNIT.convert; + } + } + } + let geogcs: GeoGCS | undefined = wkt.GEOGCS; + if (wkt.type === 'GEOGCS') { + geogcs = wkt as GeoGCS; + } + if (geogcs) { + //if(wkt.GEOGCS.PRIMEM&&wkt.GEOGCS.PRIMEM.convert){ + // wkt.from_greenwich=wkt.GEOGCS.PRIMEM.convert*D2R; + //} + if (geogcs.DATUM) { + wkt.datumCode = geogcs.DATUM.name?.toLowerCase(); + } else { + wkt.datumCode = geogcs.name?.toLowerCase(); + } + if (wkt.datumCode?.slice(0, 2) === 'd_') { + wkt.datumCode = wkt.datumCode.slice(2); + } + if ( + wkt.datumCode === 'new_zealand_geodetic_datum_1949' || + wkt.datumCode === 'new_zealand_1949' + ) { + wkt.datumCode = 'nzgd49'; + } + if (wkt.datumCode === 'wgs_1984' || wkt.datumCode === 'world_geodetic_system_1984') { + if (wkt.PROJECTION === 'Mercator_Auxiliary_Sphere') { + wkt.sphere = true; + } + wkt.datumCode = 'wgs84'; + } + if (wkt.datumCode?.slice(-6) === '_ferro') { + wkt.datumCode = wkt.datumCode.slice(0, -6); + } + if (wkt.datumCode?.slice(-8) === '_jakarta') { + wkt.datumCode = wkt.datumCode.slice(0, -8); + } + if (~(wkt.datumCode?.indexOf('belge') ?? -1)) { + wkt.datumCode = 'rnb72'; + } + if (geogcs.DATUM && geogcs.DATUM.SPHEROID) { + wkt.ellps = geogcs.DATUM.SPHEROID.name.replace('_19', '').replace(/[Cc]larke_18/, 'clrk'); + if (wkt.ellps.toLowerCase().slice(0, 13) === 'international') { + wkt.ellps = 'intl'; + } + + wkt.a = geogcs.DATUM.SPHEROID.a; + wkt.rf = geogcs.DATUM.SPHEROID.rf; + } + + if (geogcs.DATUM && geogcs.DATUM.TOWGS84) { + wkt.datum_params = geogcs.DATUM.TOWGS84; + } + if (~(wkt.datumCode?.indexOf('osgb_1936') ?? -1)) { + wkt.datumCode = 'osgb36'; + } + if (~(wkt.datumCode?.indexOf('osni_1952') ?? -1)) { + wkt.datumCode = 'osni52'; + } + if ( + ~(wkt.datumCode?.indexOf('tm65') ?? -1) || + ~(wkt.datumCode?.indexOf('geodetic_datum_of_1965') ?? -1) + ) { + wkt.datumCode = 'ire65'; + } + if (wkt.datumCode === 'ch1903+') { + wkt.datumCode = 'ch1903'; + } + if (~(wkt.datumCode?.indexOf('israel') ?? -1)) { + wkt.datumCode = 'isr93'; + } + } + if (wkt.b && !isFinite(wkt.b)) { + wkt.b = wkt.a; + } + /** + * @param input - meters pre-conversion + * @returns - meters + */ + const toMeter = (input: number): number => { + const ratio = wkt.to_meter ?? 1; + return input * ratio; + }; + // remaps + remap(wkt, 'standard_parallel_1', 'Standard_Parallel_1' as keyof WKTCRS); + remap(wkt, 'standard_parallel_1', 'Latitude of 1st standard parallel' as keyof WKTCRS); + remap(wkt, 'standard_parallel_2', 'Standard_Parallel_2' as keyof WKTCRS); + remap(wkt, 'standard_parallel_2', 'Latitude of 2nd standard parallel' as keyof WKTCRS); + remap(wkt, 'false_easting', 'False_Easting' as keyof WKTCRS); + remap(wkt, 'false_easting', 'easting' as keyof WKTCRS); + remap(wkt, 'false_easting', 'Easting at false origin' as keyof WKTCRS); + remap(wkt, 'false_northing', 'False_Northing' as keyof WKTCRS); + remap(wkt, 'false_northing', 'False northing' as keyof WKTCRS); + remap(wkt, 'false_northing', 'Northing at false origin' as keyof WKTCRS); + remap(wkt, 'central_meridian', 'Central_Meridian' as keyof WKTCRS); + remap(wkt, 'central_meridian', 'Longitude of natural origin' as keyof WKTCRS); + remap(wkt, 'central_meridian', 'Longitude of false origin' as keyof WKTCRS); + remap(wkt, 'latitude_of_origin', 'Latitude_Of_Origin' as keyof WKTCRS); + remap(wkt, 'latitude_of_origin', 'Central_Parallel' as keyof WKTCRS); + remap(wkt, 'latitude_of_origin', 'Latitude of natural origin' as keyof WKTCRS); + remap(wkt, 'latitude_of_origin', 'Latitude of false origin' as keyof WKTCRS); + remap(wkt, 'scale_factor', 'Scale_Factor' as keyof WKTCRS); + remap(wkt, 'k0', 'scale_factor'); + remap(wkt, 'latitude_of_center', 'Latitude_Of_Center' as keyof WKTCRS); + remap(wkt, 'latitude_of_center', 'Latitude_of_center' as keyof WKTCRS); + remap(wkt, 'lat0', 'latitude_of_center', degToRad); + remap(wkt, 'longitude_of_center', 'Longitude_Of_Center' as keyof WKTCRS); + remap(wkt, 'longitude_of_center', 'Longitude_of_center' as keyof WKTCRS); + remap(wkt, 'longc', 'longitude_of_center', degToRad); + remap(wkt, 'x0', 'false_easting', toMeter); + remap(wkt, 'y0', 'false_northing', toMeter); + remap(wkt, 'long0', 'central_meridian', degToRad); + remap(wkt, 'lat0', 'latitude_of_origin', degToRad); + remap(wkt, 'lat0', 'standard_parallel_1', degToRad); + remap(wkt, 'lat1', 'standard_parallel_1', degToRad); + remap(wkt, 'lat2', 'standard_parallel_2', degToRad); + remap(wkt, 'azimuth', 'Azimuth' as keyof WKTCRS); + remap(wkt, 'alpha', 'azimuth', degToRad); + // update long0 if applicable + if ( + wkt.long0 === undefined && + wkt.longc !== undefined && + (wkt.projName === 'Albers_Conic_Equal_Area' || wkt.projName === 'Lambert_Azimuthal_Equal_Area') + ) { + wkt.long0 = wkt.longc; + } + // update lat_ts and lat0 for polar stereographic + if ( + !wkt.lat_ts && + wkt.lat1 && + ['Stereographic_South_Pole', 'Polar Stereographic (variant B)'].includes(wkt.projName ?? '') + ) { + wkt.lat0 = degToRad(wkt.lat1 > 0 ? 90 : -90); + wkt.lat_ts = wkt.lat1; + } else if (!wkt.lat_ts && wkt.lat0 && wkt.projName === 'Polar_Stereographic') { + wkt.lat_ts = wkt.lat0; + wkt.lat0 = degToRad(wkt.lat0 > 0 ? 90 : -90); + } +} + +/** + * Only update teh to key if it did not exist + * @param input + * @param to + * @param from + * @param updateFun + */ +function remap( + input: WKTCRS, + to: keyof WKTCRS, + from: keyof WKTCRS, + updateFun?: (value: number) => number, +): void { + if (input[to] === undefined && input[from] !== undefined) { + // @ts-expect-error - its ok to remap the key + input[to] = updateFun ? updateFun(input[from] as number) : input[from]; + } +} diff --git a/src/util/base64.ts b/src/util/base64.ts new file mode 100644 index 00000000..662e32a2 --- /dev/null +++ b/src/util/base64.ts @@ -0,0 +1,13 @@ +/** + * pollyfill for string to array buffer + * @param base64 - base64 encoded string + * @returns converted ArrayBuffer of the string data + */ +export function base64ToArrayBuffer(base64: string): ArrayBuffer { + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i); + + return bytes.buffer as ArrayBuffer; +} diff --git a/src/util/gzip.ts b/src/util/gzip.ts index f9326fec..7b820c0a 100644 --- a/src/util/gzip.ts +++ b/src/util/gzip.ts @@ -9,8 +9,10 @@ export type Format = 'deflate' | 'deflate-raw' | 'gzip'; */ export async function decompressStream( uint8Array: Uint8Array, - format: Format = 'gzip', + format?: Format, ): Promise { + if (format === undefined) + format = uint8Array[0] === 0x1f && uint8Array[1] === 0x8b ? 'gzip' : 'deflate'; // Create a DecompressionStream for 'gzip' const decompressionStream = new DecompressionStream(format); // Convert the Uint8Array to a readable stream diff --git a/src/wasm/uint64.ts b/src/wasm/uint64.ts index 99f57540..48ac7832 100644 --- a/src/wasm/uint64.ts +++ b/src/wasm/uint64.ts @@ -1,6 +1,7 @@ +import { base64ToArrayBuffer } from '../util/base64'; import wasmBase64 from './uint64.wasm'; -import { S2Point, fromST } from '../geometry'; +import { S2Point, fromS2Point as fromS2, fromST } from '../geometry'; import type { Face, Point3D, S2CellId } from '../geometry'; @@ -80,7 +81,8 @@ export default class Uint64CellGenerator { * @returns - an Uint64Cell with the appropriate id and functions */ fromS2Point(point: Point3D): Uint64Cell { - // TODO: + const id = fromS2(point); + return this.fromBigInt(id); } /** @@ -91,7 +93,7 @@ export default class Uint64CellGenerator { fromBigInt(id: S2CellId): Uint64Cell { const low = Number(id & 0xffffffffn); const high = Number(id >> 32n); - return this.#fromLowHigh(low, high); + return this.fromLowHigh(low, high); } /** @@ -100,7 +102,7 @@ export default class Uint64CellGenerator { * @param high - high 32 bits * @returns - an Uint64Cell with the appropriate id and functions */ - #fromLowHigh(low: number, high: number): Uint64Cell { + fromLowHigh(low: number, high: number): Uint64Cell { const _fromLowHigh = this.instance.exports.from_low_high as WasmFromLowHigh; const _comparitor = this.instance.exports.compare_uint64 as WasmUint64Comparitor; const id = _fromLowHigh(low, high); @@ -121,17 +123,3 @@ export default class Uint64CellGenerator { return cell; } } - -/** - * pollyfill for string to array buffer - * @param base64 - base64 encoded string - * @returns converted ArrayBuffer of the string data - */ -function base64ToArrayBuffer(base64: string): ArrayBuffer { - const binaryString = atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i); - - return bytes.buffer as ArrayBuffer; -} diff --git a/tests/readers/README.md b/tests/readers/README.md new file mode 100644 index 00000000..ef9f8166 --- /dev/null +++ b/tests/readers/README.md @@ -0,0 +1,9 @@ +# Readers + +## Readers I am interested in eventually supporting + +- [ ] CSV +- [ ] XML +- [ ] [flatgeobuf](https://flatgeobuf.org/) & flatbuffers +- [ ] GTFS +- [ ] netCDF (currently only partially supported via nadgrid) diff --git a/tests/readers/fixtures/manyNodes.pbf b/tests/readers/fixtures/manyNodes.pbf new file mode 100644 index 00000000..2b9534e5 Binary files /dev/null and b/tests/readers/fixtures/manyNodes.pbf differ diff --git a/tests/readers/fixtures/point-feature.geojson b/tests/readers/fixtures/point-feature.geojson new file mode 100644 index 00000000..12aef4f2 --- /dev/null +++ b/tests/readers/fixtures/point-feature.geojson @@ -0,0 +1 @@ +{"type":"Feature","properties":{"name":"Melbourne"},"geometry":{"type":"Point","coordinates":[144.9584,-37.8173]}} diff --git a/tests/readers/fixtures/points.geojson b/tests/readers/fixtures/points.geojson new file mode 100644 index 00000000..80e61caa --- /dev/null +++ b/tests/readers/fixtures/points.geojson @@ -0,0 +1,8 @@ +{ + "type": "FeatureCollection", + "features": [ + {"type":"Feature","properties":{"name":"Melbourne"},"geometry":{"type":"Point","coordinates":[144.9584,-37.8173]}}, + {"type":"Feature","properties":{"name":"Canberra"},"geometry":{"type":"Point","coordinates":[149.1009,-35.3039]}}, + {"type":"Feature","properties":{"name":"Sydney"},"geometry":{"type":"Point","coordinates":[151.2144,-33.8766]}} + ] +} diff --git a/tests/readers/fixtures/points.geojsonld b/tests/readers/fixtures/points.geojsonld new file mode 100644 index 00000000..7ae14a13 --- /dev/null +++ b/tests/readers/fixtures/points.geojsonld @@ -0,0 +1,3 @@ +{"type":"Feature","properties":{"name":"Melbourne"},"geometry":{"type":"Point","coordinates":[144.9584,-37.8173]}} +{"type":"Feature","properties":{"name":"Canberra"},"geometry":{"type":"Point","coordinates":[149.1009,-35.3039]}} +{"type":"Feature","properties":{"name":"Sydney"},"geometry":{"type":"Point","coordinates":[151.2144,-33.8766]}} diff --git a/tests/readers/fixtures/test.pbf b/tests/readers/fixtures/test.pbf new file mode 100644 index 00000000..8ac43d7a Binary files /dev/null and b/tests/readers/fixtures/test.pbf differ diff --git a/tests/readers/json/index.test.ts b/tests/readers/json/index.test.ts new file mode 100644 index 00000000..cb680ab9 --- /dev/null +++ b/tests/readers/json/index.test.ts @@ -0,0 +1,189 @@ +import { BufferReader } from '../../../src/readers'; +import FileReader from '../../../src/readers/fileReader'; +import { + BufferJSONReader, + JSONReader, + NewLineDelimitedJSONReader, +} from '../../../src/readers/json'; +import { expect, test } from 'bun:test'; + +test('BufferJSONReader - string', async () => { + const file = await Bun.file('tests/readers/fixtures/points.geojson').arrayBuffer(); + const buffer = Buffer.from(file); + const reader = new BufferJSONReader(buffer.toString('utf-8')); + const data = [...reader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('BufferJSONReader - object', async () => { + const json = await Bun.file('tests/readers/fixtures/points.geojson').json(); + const reader = new BufferJSONReader(json); + const data = [...reader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('NewLineDelimitedJSONReader - BufferReader', async () => { + const fileBuf = await Bun.file('tests/readers/fixtures/points.geojsonld').arrayBuffer(); + const bufReader = new BufferReader(fileBuf); + const ldReader = new NewLineDelimitedJSONReader(bufReader); + const data = [...ldReader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('NewLineDelimitedJSONReader - FileReader', async () => { + const fileReader = new FileReader('tests/readers/fixtures/points.geojsonld'); + const ldReader = new NewLineDelimitedJSONReader(fileReader); + const data = [...ldReader.iterate()]; + fileReader.close(); + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('JSONReader - BufferReader', async () => { + const fileBuf = await Bun.file('tests/readers/fixtures/points.geojson').arrayBuffer(); + const bufReader = new BufferReader(fileBuf); + const reader = new JSONReader(bufReader); + const data = [...reader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('JSONReader - BufferReader (forced "large" read)', async () => { + const fileBuf = await Bun.file('tests/readers/fixtures/points.geojson').arrayBuffer(); + const bufReader = new BufferReader(fileBuf); + const reader = new JSONReader(bufReader, 1); + const data = [...reader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('JSONReader - FileReader', async () => { + const fileReader = new FileReader('tests/readers/fixtures/points.geojson'); + const reader = new JSONReader(fileReader); + const data = [...reader.iterate()]; + fileReader.close(); + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + { + geometry: { coordinates: [149.1009, -35.3039], type: 'Point' }, + properties: { name: 'Canberra' }, + type: 'Feature', + }, + { + geometry: { coordinates: [151.2144, -33.8766], type: 'Point' }, + properties: { name: 'Sydney' }, + type: 'Feature', + }, + ]); +}); + +test('JSONReader - BufferReader', async () => { + const fileBuf = await Bun.file('tests/readers/fixtures/point-feature.geojson').arrayBuffer(); + const bufReader = new BufferReader(fileBuf); + const reader = new JSONReader(bufReader); + const data = [...reader.iterate()]; + expect(data).toEqual([ + { + geometry: { coordinates: [144.9584, -37.8173], type: 'Point' }, + properties: { name: 'Melbourne' }, + type: 'Feature', + }, + ]); +}); diff --git a/tests/readers/mmapReader.test.ts b/tests/readers/mmapReader.test.ts new file mode 100644 index 00000000..247d0cc8 --- /dev/null +++ b/tests/readers/mmapReader.test.ts @@ -0,0 +1,32 @@ +import MMapReader from '../../src/readers/mmapReader'; +import { expect, test } from 'bun:test'; + +test('MMapReader', () => { + const reader = new MMapReader('tests/readers/fixtures/dv.bin'); + reader.setStringEncoding('utf-8'); + + let offset = 0; + expect(reader.getUint8(offset)).toBe(255); + offset += 1; + expect(reader.getUint16(offset, true)).toBe(65535); + offset += 2; + expect(reader.getUint32(offset, true)).toBe(4294967295); + offset += 4; + expect(reader.getInt8(offset)).toBe(-128); + offset += 1; + expect(reader.getInt16(offset, true)).toBe(-32768); + offset += 2; + expect(reader.getInt32(offset, true)).toBe(-2147483648); + offset += 4; + expect(reader.getFloat32(offset, true)).toBe(3.140000104904175); + offset += 4; + expect(reader.getFloat64(offset, true)).toBe(3.14159265359); + offset += 8; + expect(reader.getBigUint64(offset, true)).toBe(12345678901234567890n); + offset += 8; + expect(reader.getBigInt64(offset, true)).toBe(-1234567890123456789n); + + expect(() => { + reader.slice(1_000, 2_000); + }).toThrowError('Invalid slice range'); +}); diff --git a/tests/readers/osm/index.test.ts b/tests/readers/osm/index.test.ts new file mode 100644 index 00000000..0d97be2a --- /dev/null +++ b/tests/readers/osm/index.test.ts @@ -0,0 +1,193 @@ +import FileReader from '../../../src/readers/fileReader'; +import { OSMReader } from '../../../src/readers/osm'; +import { expect, test } from 'bun:test'; + +test('parse basic case', async () => { + const fileReader = new FileReader('tests/readers/fixtures/test.pbf'); + + const reader = new OSMReader(fileReader, { removeEmptyNodes: false }); + const header = await reader.getHeader(); + expect(header).toEqual({ + bbox: [-1, -1, -1, -1], + optional_features: [], + osmosis_replication_base_url: undefined, + osmosis_replication_sequence_number: -1, + osmosis_replication_timestamp: -1, + required_features: ['-x�S��/�\rN�H�M�\r3�3S�rI�+N'], + source: undefined, + writingprogram: undefined, + }); + const features = await Array.fromAsync(reader.iterate()); + + expect(features.length).toBe(8); + expect(features).toEqual([ + { + geometry: { + coordinates: { x: -0.1080108, y: 51.5074089 }, + is3D: false, + type: 'Point', + }, + id: 319408586, + metadata: { + changeset: 440330, + timestamp: 1229476722000, + uid: 6871, + user: 'name', + version: -1, + visible: true, + }, + properties: {}, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: { x: -0.10812640000000001, y: 51.5074343 }, + is3D: false, + type: 'Point', + }, + id: 319408587, + metadata: { + changeset: 0, + timestamp: 1229476722000, + uid: 6871, + user: '', + version: -1, + visible: true, + }, + properties: {}, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: { x: -0.10761860000000001, y: 51.5075933 }, + is3D: false, + type: 'Point', + }, + id: 275452090, + metadata: { + changeset: 2540257, + timestamp: 1256818475000, + uid: 1697, + user: 'service', + version: -2, + visible: true, + }, + properties: { + amenity: 'cafe', + name: "Jam's Sandwich Bar", + }, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: { x: -0.1075735, y: 51.507464500000005 }, + is3D: false, + type: 'Point', + }, + id: 304994980, + metadata: { + changeset: -2591627, + timestamp: 1234485707000, + uid: 3516, + user: 'private', + version: 1, + visible: true, + }, + properties: { + barrier: 'gate', + }, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: { x: -0.10750140000000001, y: 51.5074723 }, + is3D: false, + type: 'Point', + }, + id: 304994981, + metadata: { + changeset: -14817, + timestamp: 1224174957000, + uid: 70, + version: -1, + visible: true, + }, + properties: {}, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: { x: -0.10833480000000001, y: 51.507406 }, + is3D: false, + type: 'Point', + }, + id: 304994979, + metadata: { + changeset: 1739860, + timestamp: 1250040812000, + uid: 38244, + version: 2, + visible: true, + }, + properties: {}, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: [ + { x: -0.10833480000000001, y: 51.507406 }, + { x: -0.10812640000000001, y: 51.5074343 }, + { x: -0.1080108, y: 51.5074089 }, + { x: -0.1075735, y: 51.507464500000005 }, + { x: -0.10750140000000001, y: 51.5074723 }, + ], + is3D: false, + type: 'LineString', + }, + id: 27776903, + metadata: { + user: 'Matt', + changeset: 684276, + timestamp: -621888578000, + uid: 35, + version: -2, + visible: true, + }, + properties: { + access: 'private', + highway: 'service', + name: 'üßé€', + }, + type: 'VectorFeature', + }, + { + geometry: { + coordinates: [ + { x: -0.10833480000000001, y: 51.507406 }, + { x: -0.10812640000000001, y: 51.5074343 }, + { x: -0.1080108, y: 51.5074089 }, + { x: -0.1075735, y: 51.507464500000005 }, + { x: -0.10750140000000001, y: 51.5074723 }, + ], + is3D: false, + type: 'LineString', + }, + id: 56688, + metadata: { + changeset: -3473819, + timestamp: -647421115000, + uid: 28095, + user: 'kmvar', + version: 14, + visible: true, + }, + properties: { + network: 'VVW', + ref: '123', + route: 'bus', + type: 'route', + }, + type: 'VectorFeature', + }, + ]); +}); diff --git a/tests/readers/shapefile/dbf.test.ts b/tests/readers/shapefile/dbf.test.ts index c3495125..f51223d1 100644 --- a/tests/readers/shapefile/dbf.test.ts +++ b/tests/readers/shapefile/dbf.test.ts @@ -1,6 +1,7 @@ import { BufferReader } from '../../../src/readers'; import DataBaseFile from '../../../src/readers/shapefile/dbf'; import FileReader from '../../../src/readers/fileReader'; +import MMapReader from '../../../src/readers/mmapReader'; import { expect, test } from 'bun:test'; import { readFile } from 'fs/promises'; @@ -48,7 +49,7 @@ test('utf dbf', async () => { }); test('watershed dbf', async () => { - const data = new BufferReader((await readFile('tests/readers/fixtures/watershed.dbf')).buffer); + const data = new MMapReader('tests/readers/fixtures/watershed.dbf'); const dbf = new DataBaseFile(data, 'utf-8'); expect(dbf.getHeader()).toEqual({ diff --git a/tests/readers/shapefile/shp.test.ts b/tests/readers/shapefile/shp.test.ts index 7d1972a9..a8e4a844 100644 --- a/tests/readers/shapefile/shp.test.ts +++ b/tests/readers/shapefile/shp.test.ts @@ -1,6 +1,7 @@ import { BufferReader } from '../../../src/readers'; import DataBaseFile from '../../../src/readers/shapefile/dbf'; import FileReader from '../../../src/readers/fileReader'; +import MMapReader from '../../../src/readers/mmapReader'; import ShapeFile from '../../../src/readers/shapefile/shp'; import { expect, test } from 'bun:test'; @@ -102,9 +103,7 @@ test('utf shp - file', async () => { }); test('multipointz shp', async () => { - const data = new BufferReader( - (await readFile('tests/readers/fixtures/export_multipointz.shp')).buffer, - ); + const data = new MMapReader('tests/readers/fixtures/export_multipointz.shp'); const shp = new ShapeFile(data); expect(shp.getHeader()).toEqual({ diff --git a/tests/readers/wkt/index.test.ts b/tests/readers/wkt/index.test.ts new file mode 100644 index 00000000..3c4ea291 --- /dev/null +++ b/tests/readers/wkt/index.test.ts @@ -0,0 +1,969 @@ +import { expect, test } from 'bun:test'; +import { parseWKTGeometry, parseWKTObject, parseWKTProjection } from '../../../src/readers/wkt'; + +test('parse geometry point', () => { + const wktStr = 'POINT(0 0)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'Point', + is3D: false, + coordinates: { x: 0, y: 0 }, + }); + + const wktStr2 = 'POINT(5.4321 1.2345)'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'Point', + is3D: false, + coordinates: { x: 5.4321, y: 1.2345 }, + }); +}); + +test('parse geometry point 3D', () => { + const wktStr = 'POINT Z (0 0 0)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'Point', + is3D: true, + coordinates: { x: 0, y: 0, z: 0 }, + }); + + const wktStr2 = 'POINT Z (5.4321 1.2345 2.3456)'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'Point', + is3D: true, + coordinates: { x: 5.4321, y: 1.2345, z: 2.3456 }, + }); +}); + +test('parse geometry multipoint', () => { + const wktStr = 'MULTIPOINT (30 10, 10 30, 40 40)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiPoint', + is3D: false, + coordinates: [ + { x: 30, y: 10 }, + { x: 10, y: 30 }, + { x: 40, y: 40 }, + ], + }); + + const wktStr2 = 'MULTIPOINT ((30 10), (10 30), (40 40))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiPoint', + is3D: false, + coordinates: [ + { x: 30, y: 10 }, + { x: 10, y: 30 }, + { x: 40, y: 40 }, + ], + }); +}); + +test('parse geometry multipoint 3D', () => { + const wktStr = 'MULTIPOINT Z (30 10 0, 10 30 1, 40 40 2)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiPoint', + is3D: true, + coordinates: [ + { x: 30, y: 10, z: 0 }, + { x: 10, y: 30, z: 1 }, + { x: 40, y: 40, z: 2 }, + ], + }); + + const wktStr2 = 'MULTIPOINT Z ((30 10 0), (10 30 1), (40 40 2))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiPoint', + is3D: true, + coordinates: [ + { x: 30, y: 10, z: 0 }, + { x: 10, y: 30, z: 1 }, + { x: 40, y: 40, z: 2 }, + ], + }); +}); + +test('parse geometry linestring', () => { + const wktStr = 'LINESTRING (30 10, 10 30, 40 40)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'LineString', + is3D: false, + coordinates: [ + { x: 30, y: 10 }, + { x: 10, y: 30 }, + { x: 40, y: 40 }, + ], + }); + + const wktStr2 = 'LINESTRING ((30 10), (10 30), (40 40))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'LineString', + is3D: false, + coordinates: [ + { x: 30, y: 10 }, + { x: 10, y: 30 }, + { x: 40, y: 40 }, + ], + }); +}); + +test('parse geometry linestring 3D', () => { + const wktStr = 'LINESTRING Z (30 10 0, 10 30 1, 40 40 2)'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'LineString', + is3D: true, + coordinates: [ + { x: 30, y: 10, z: 0 }, + { x: 10, y: 30, z: 1 }, + { x: 40, y: 40, z: 2 }, + ], + }); + + const wktStr2 = 'LINESTRING Z ((30 10 0), (10 30 1), (40 40 2))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'LineString', + is3D: true, + coordinates: [ + { x: 30, y: 10, z: 0 }, + { x: 10, y: 30, z: 1 }, + { x: 40, y: 40, z: 2 }, + ], + }); +}); + +test('parse geometry multilinestring', () => { + const wktStr = 'MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiLineString', + is3D: false, + coordinates: [ + [ + { x: 10, y: 10 }, + { x: 20, y: 20 }, + { x: 10, y: 40 }, + ], + [ + { x: 40, y: 40 }, + { x: 30, y: 30 }, + { x: 40, y: 20 }, + { x: 30, y: 10 }, + ], + ], + }); + + const wktStr2 = + 'MULTILINESTRING (((10 10), (20 20), (10 40)),((40 40), (30 30), (40 20), (30 10)))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiLineString', + is3D: false, + coordinates: [ + [ + { x: 10, y: 10 }, + { x: 20, y: 20 }, + { x: 10, y: 40 }, + ], + [ + { x: 40, y: 40 }, + { x: 30, y: 30 }, + { x: 40, y: 20 }, + { x: 30, y: 10 }, + ], + ], + }); +}); + +test('parse geometry multilinestring 3D', () => { + const wktStr = + 'MULTILINESTRING Z ((10 10 1, 20 20 2, 10 40 3),(40 40 4, 30 30 5, 40 20 6, 30 10 7))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiLineString', + is3D: true, + coordinates: [ + [ + { x: 10, y: 10, z: 1 }, + { x: 20, y: 20, z: 2 }, + { x: 10, y: 40, z: 3 }, + ], + [ + { x: 40, y: 40, z: 4 }, + { x: 30, y: 30, z: 5 }, + { x: 40, y: 20, z: 6 }, + { x: 30, y: 10, z: 7 }, + ], + ], + }); + + const wktStr2 = + 'MULTILINESTRING Z (((10 10 1), (20 20 2), (10 40 3)),((40 40 4), (30 30 5), (40 20 6), (30 10 7)))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiLineString', + is3D: true, + coordinates: [ + [ + { x: 10, y: 10, z: 1 }, + { x: 20, y: 20, z: 2 }, + { x: 10, y: 40, z: 3 }, + ], + [ + { x: 40, y: 40, z: 4 }, + { x: 30, y: 30, z: 5 }, + { x: 40, y: 20, z: 6 }, + { x: 30, y: 10, z: 7 }, + ], + ], + }); +}); + +test('parse geometry polygon', () => { + const wktStr = 'POLYGON ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'Polygon', + is3D: false, + coordinates: [ + [ + { x: 10, y: 10 }, + { x: 20, y: 20 }, + { x: 10, y: 40 }, + ], + [ + { x: 40, y: 40 }, + { x: 30, y: 30 }, + { x: 40, y: 20 }, + { x: 30, y: 10 }, + ], + ], + }); + + const wktStr2 = 'POLYGON (((10 10), (20 20), (10 40)),((40 40), (30 30), (40 20), (30 10)))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'Polygon', + is3D: false, + coordinates: [ + [ + { x: 10, y: 10 }, + { x: 20, y: 20 }, + { x: 10, y: 40 }, + ], + [ + { x: 40, y: 40 }, + { x: 30, y: 30 }, + { x: 40, y: 20 }, + { x: 30, y: 10 }, + ], + ], + }); +}); + +test('parse geometry polygon 3D', () => { + const wktStr = 'POLYGON Z ((10 10 1, 20 20 2, 10 40 3),(40 40 4, 30 30 5, 40 20 6, 30 10 7))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'Polygon', + is3D: true, + coordinates: [ + [ + { x: 10, y: 10, z: 1 }, + { x: 20, y: 20, z: 2 }, + { x: 10, y: 40, z: 3 }, + ], + [ + { x: 40, y: 40, z: 4 }, + { x: 30, y: 30, z: 5 }, + { x: 40, y: 20, z: 6 }, + { x: 30, y: 10, z: 7 }, + ], + ], + }); + + const wktStr2 = + 'POLYGON Z (((10 10 1), (20 20 2), (10 40 3)),((40 40 4), (30 30 5), (40 20 6), (30 10 7)))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'Polygon', + is3D: true, + coordinates: [ + [ + { x: 10, y: 10, z: 1 }, + { x: 20, y: 20, z: 2 }, + { x: 10, y: 40, z: 3 }, + ], + [ + { x: 40, y: 40, z: 4 }, + { x: 30, y: 30, z: 5 }, + { x: 40, y: 20, z: 6 }, + { x: 30, y: 10, z: 7 }, + ], + ], + }); +}); + +test('parse geometry multipolygon', () => { + const wktStr = + 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiPolygon', + is3D: false, + coordinates: [ + [ + [ + { x: 40, y: 40 }, + { x: 20, y: 45 }, + { x: 45, y: 30 }, + { x: 40, y: 40 }, + ], + ], + [ + [ + { x: 20, y: 35 }, + { x: 10, y: 30 }, + { x: 10, y: 10 }, + { x: 30, y: 5 }, + { x: 45, y: 20 }, + { x: 20, y: 35 }, + ], + [ + { x: 30, y: 20 }, + { x: 20, y: 15 }, + { x: 20, y: 25 }, + { x: 30, y: 20 }, + ], + ], + ], + }); + + const wktStr2 = + 'MULTIPOLYGON ((((40 40), (20 45), (45 30), (40 40))),(((20 35), (10 30), (10 10), (30 5), (45 20), (20 35)),((30 20), (20 15), (20 25), (30 20))))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiPolygon', + is3D: false, + coordinates: [ + [ + [ + { x: 40, y: 40 }, + { x: 20, y: 45 }, + { x: 45, y: 30 }, + { x: 40, y: 40 }, + ], + ], + [ + [ + { x: 20, y: 35 }, + { x: 10, y: 30 }, + { x: 10, y: 10 }, + { x: 30, y: 5 }, + { x: 45, y: 20 }, + { x: 20, y: 35 }, + ], + [ + { x: 30, y: 20 }, + { x: 20, y: 15 }, + { x: 20, y: 25 }, + { x: 30, y: 20 }, + ], + ], + ], + }); +}); + +test('parse geometry multipolygon 3D', () => { + const wktStr = + 'MULTIPOLYGON Z (((40 40 0, 20 45 1, 45 30 2, 40 40 3)),((20 35 4, 10 30 5, 10 10 6, 30 5 7, 45 20 8, 20 35 9),(30 20 10, 20 15 11, 20 25 12, 30 20 13)))'; + const geo = parseWKTGeometry(wktStr); + expect(geo).toEqual({ + type: 'MultiPolygon', + is3D: true, + coordinates: [ + [ + [ + { x: 40, y: 40, z: 0 }, + { x: 20, y: 45, z: 1 }, + { x: 45, y: 30, z: 2 }, + { x: 40, y: 40, z: 3 }, + ], + ], + [ + [ + { x: 20, y: 35, z: 4 }, + { x: 10, y: 30, z: 5 }, + { x: 10, y: 10, z: 6 }, + { x: 30, y: 5, z: 7 }, + { x: 45, y: 20, z: 8 }, + { x: 20, y: 35, z: 9 }, + ], + [ + { x: 30, y: 20, z: 10 }, + { x: 20, y: 15, z: 11 }, + { x: 20, y: 25, z: 12 }, + { x: 30, y: 20, z: 13 }, + ], + ], + ], + }); + + const wktStr2 = + 'MULTIPOLYGON Z ((((40 40 0), (20 45 1), (45 30 2), (40 40 3))),(((20 35 4), (10 30 5), (10 10 6), (30 5 7), (45 20 8), (20 35 9)),((30 20 10), (20 15 11), (20 25 12), (30 20 13))))'; + const geo2 = parseWKTGeometry(wktStr2); + expect(geo2).toEqual({ + type: 'MultiPolygon', + is3D: true, + coordinates: [ + [ + [ + { x: 40, y: 40, z: 0 }, + { x: 20, y: 45, z: 1 }, + { x: 45, y: 30, z: 2 }, + { x: 40, y: 40, z: 3 }, + ], + ], + [ + [ + { x: 20, y: 35, z: 4 }, + { x: 10, y: 30, z: 5 }, + { x: 10, y: 10, z: 6 }, + { x: 30, y: 5, z: 7 }, + { x: 45, y: 20, z: 8 }, + { x: 20, y: 35, z: 9 }, + ], + [ + { x: 30, y: 20, z: 10 }, + { x: 20, y: 15, z: 11 }, + { x: 20, y: 25, z: 12 }, + { x: 30, y: 20, z: 13 }, + ], + ], + ], + }); +}); + +test('parseWKTObject base case', () => { + const wktStr = + 'PROJCS["NZGD49 / New Zealand Map Grid",GEOGCS["NZGD49",DATUM["New_Zealand_Geodetic_Datum_1949",SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]],TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["New_Zealand_Map_Grid"],PARAMETER["latitude_of_origin",-41],PARAMETER["central_meridian",173],PARAMETER["false_easting",2510000],PARAMETER["false_northing",6023150],AUTHORITY["EPSG","27200"],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'; + const obj = parseWKTObject(wktStr); + expect(obj).toEqual([ + 'PROJCS', + [ + 'NZGD49 / New Zealand Map Grid', + 'GEOGCS', + [ + 'NZGD49', + 'DATUM', + [ + 'New_Zealand_Geodetic_Datum_1949', + 'SPHEROID', + ['International 1924', '6378388', '297', 'AUTHORITY', ['EPSG', '7022']], + 'TOWGS84', + ['59.47', '-5.04', '187.44', '0.47', '-0.1', '1.024', '-4.5993'], + 'AUTHORITY', + ['EPSG', '6272'], + ], + 'PRIMEM', + ['Greenwich', '0', 'AUTHORITY', ['EPSG', '8901']], + 'UNIT', + ['degree', '0.01745329251994328', 'AUTHORITY', ['EPSG', '9122']], + 'AUTHORITY', + ['EPSG', '4272'], + ], + 'UNIT', + ['metre', '1', 'AUTHORITY', ['EPSG', '9001']], + 'PROJECTION', + ['New_Zealand_Map_Grid'], + 'PARAMETER', + ['latitude_of_origin', '-41'], + 'PARAMETER', + ['central_meridian', '173'], + 'PARAMETER', + ['false_easting', '2510000'], + 'PARAMETER', + ['false_northing', '6023150'], + 'AUTHORITY', + ['EPSG', '27200'], + 'AXIS', + ['Easting', 'EAST'], + 'AXIS', + ['Northing', 'NORTH'], + ], + ]); +}); + +test('parse fixture 0', () => { + expect( + parseWKTProjection( + 'PROJCS["NZGD49 / New Zealand Map Grid",GEOGCS["NZGD49",DATUM["New_Zealand_Geodetic_Datum_1949",SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]],TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["New_Zealand_Map_Grid"],PARAMETER["latitude_of_origin",-41],PARAMETER["central_meridian",173],PARAMETER["false_easting",2510000],PARAMETER["false_northing",6023150],AUTHORITY["EPSG","27200"],AXIS["Easting",EAST],AXIS["Northing",NORTH]]', + ), + ).toEqual({ + type: 'PROJCS', + name: 'NZGD49 / New Zealand Map Grid', + GEOGCS: { + name: 'NZGD49', + DATUM: { + name: 'New_Zealand_Geodetic_Datum_1949', + SPHEROID: { + name: 'International 1924', + a: 6378388, + rf: 297, + AUTHORITY: { + EPSG: '7022', + }, + }, + TOWGS84: [59.47, -5.04, 187.44, 0.47, -0.1, 1.024, -4.5993], + AUTHORITY: { + EPSG: '6272', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '8901', + }, + }, + UNIT: { + name: 'degree', + convert: 0.01745329251994328, + AUTHORITY: { + EPSG: '9122', + }, + }, + AUTHORITY: { + EPSG: '4272', + }, + }, + UNIT: { + name: 'metre', + convert: 1, + AUTHORITY: { + EPSG: '9001', + }, + }, + PROJECTION: 'New_Zealand_Map_Grid', + latitude_of_origin: -41, + central_meridian: 173, + false_easting: 2510000, + false_northing: 6023150, + AUTHORITY: { + EPSG: '27200', + }, + AXIS: [ + ['Easting', 'EAST'], + ['Northing', 'NORTH'], + ], + projName: 'New_Zealand_Map_Grid', + units: 'meter', + to_meter: 1, + datumCode: 'nzgd49', + datum_params: [59.47, -5.04, 187.44, 0.47, -0.1, 1.024, -4.5993], + ellps: 'intl', + a: 6378388, + rf: 297, + x0: 2510000, + y0: 6023150, + long0: 3.01941960595019, + lat0: -0.715584993317675, + axis: 'enu', + srsCode: 'NZGD49 / New Zealand Map Grid', + }); +}); + +test('parse fixture 1', () => { + expect( + parseWKTProjection( + 'PROJCS["NAD83 / Massachusetts Mainland",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",42.68333333333333],PARAMETER["standard_parallel_2",41.71666666666667],PARAMETER["latitude_of_origin",41],PARAMETER["central_meridian",-71.5],PARAMETER["false_easting",200000],PARAMETER["false_northing",750000],AUTHORITY["EPSG","26986"],AXIS["X",EAST],AXIS["Y",NORTH]]', + ), + ).toEqual({ + type: 'PROJCS', + name: 'NAD83 / Massachusetts Mainland', + GEOGCS: { + name: 'NAD83', + DATUM: { + name: 'North_American_Datum_1983', + SPHEROID: { + name: 'GRS 1980', + a: 6378137, + rf: 298.257222101, + AUTHORITY: { + EPSG: '7019', + }, + }, + AUTHORITY: { + EPSG: '6269', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '8901', + }, + }, + UNIT: { + name: 'degree', + convert: 0.01745329251994328, + AUTHORITY: { + EPSG: '9122', + }, + }, + AUTHORITY: { + EPSG: '4269', + }, + }, + UNIT: { + name: 'metre', + convert: 1, + AUTHORITY: { + EPSG: '9001', + }, + }, + PROJECTION: 'Lambert_Conformal_Conic_2SP', + standard_parallel_1: 42.68333333333333, + standard_parallel_2: 41.71666666666667, + latitude_of_origin: 41, + central_meridian: -71.5, + false_easting: 200000, + false_northing: 750000, + AUTHORITY: { + EPSG: '26986', + }, + AXIS: [ + ['X', 'EAST'], + ['Y', 'NORTH'], + ], + projName: 'Lambert_Conformal_Conic_2SP', + units: 'meter', + to_meter: 1, + datumCode: 'north_american_datum_1983', + ellps: 'GRS 1980', + a: 6378137, + rf: 298.257222101, + x0: 200000, + y0: 750000, + long0: -1.2479104151759457, + lat0: 0.715584993317675, + lat1: 0.744964702392913, + lat2: 0.7280931862903011, + axis: 'enu', + srsCode: 'NAD83 / Massachusetts Mainland', + }); +}); + +test('parse fixture 2', () => { + expect( + parseWKTProjection( + 'PROJCS["ETRS89 / ETRS-LAEA",GEOGCS["ETRS89",DATUM["European_Terrestrial_Reference_System_1989",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4258"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Azimuthal_Equal_Area"],PARAMETER["latitude_of_center",52],PARAMETER["longitude_of_center",10],PARAMETER["false_easting",4321000],PARAMETER["false_northing",3210000],AUTHORITY["EPSG","3035"],AXIS["X",EAST],AXIS["Y",NORTH]]', + ), + ).toEqual({ + type: 'PROJCS', + name: 'ETRS89 / ETRS-LAEA', + GEOGCS: { + name: 'ETRS89', + DATUM: { + name: 'European_Terrestrial_Reference_System_1989', + SPHEROID: { + name: 'GRS 1980', + a: 6378137, + rf: 298.257222101, + AUTHORITY: { + EPSG: '7019', + }, + }, + AUTHORITY: { + EPSG: '6258', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '8901', + }, + }, + UNIT: { + name: 'degree', + convert: 0.01745329251994328, + AUTHORITY: { + EPSG: '9122', + }, + }, + AUTHORITY: { + EPSG: '4258', + }, + }, + UNIT: { + name: 'metre', + convert: 1, + AUTHORITY: { + EPSG: '9001', + }, + }, + PROJECTION: 'Lambert_Azimuthal_Equal_Area', + latitude_of_center: 52, + longitude_of_center: 10, + false_easting: 4321000, + false_northing: 3210000, + AUTHORITY: { + EPSG: '3035', + }, + AXIS: [ + ['X', 'EAST'], + ['Y', 'NORTH'], + ], + projName: 'Lambert_Azimuthal_Equal_Area', + axis: 'enu', + units: 'meter', + to_meter: 1, + datumCode: 'european_terrestrial_reference_system_1989', + ellps: 'GRS 1980', + a: 6378137, + rf: 298.257222101, + lat0: 0.9075712110370514, + longc: 0.17453292519943295, + x0: 4321000, + y0: 3210000, + srsCode: 'ETRS89 / ETRS-LAEA', + long0: 0.17453292519943295, + }); +}); + +test('parse fixture 3', () => { + expect( + parseWKTProjection( + 'GEOGCS["ETRS89",DATUM["European Terrestrial Reference System 1989 ensemble",SPHEROID["GRS 1980",6378137,298.2572221,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6258"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9102"]],AXIS["Lat",north],AXIS["Lon",east],AUTHORITY["EPSG","4258"]]', + ), + ).toEqual({ + type: 'GEOGCS', + name: 'ETRS89', + DATUM: { + name: 'European Terrestrial Reference System 1989 ensemble', + SPHEROID: { + name: 'GRS 1980', + a: 6378137, + rf: 298.2572221, + AUTHORITY: { + EPSG: '7019', + }, + }, + AUTHORITY: { + EPSG: '6258', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '8901', + }, + }, + UNIT: { + name: 'degree', + convert: 0.0174532925199433, + AUTHORITY: { + EPSG: '9102', + }, + }, + AXIS: [ + ['Lat', 'north'], + ['Lon', 'east'], + ], + AUTHORITY: { + EPSG: '4258', + }, + projName: 'longlat', + units: 'degree', + to_meter: 111319.4907932736, + datumCode: 'european terrestrial reference system 1989 ensemble', + ellps: 'GRS 1980', + a: 6378137, + rf: 298.2572221, + axis: 'neu', + srsCode: 'ETRS89', + }); +}); + +test('parse fixture 4', () => { + expect( + parseWKTProjection( + 'COMPD_CS["unknown",PROJCS["NAD83 / Texas North Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101004,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["latitude_of_origin",31.6666666666667],PARAMETER["central_meridian",-98.5],PARAMETER["standard_parallel_1",32.1333333333333],PARAMETER["standard_parallel_2",33.9666666666667],PARAMETER["false_easting",1968500],PARAMETER["false_northing",6561666.66666667],UNIT["US survey foot",0.304800609601219,AUTHORITY["EPSG","9003"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32138"]],VERT_CS["NAVD88 height (ftUS)",VERT_DATUM["North American Vertical Datum 1988",2005,AUTHORITY["EPSG","5103"]],UNIT["US survey foot",0.304800609601219,AUTHORITY["EPSG","9003"]],AXIS["Up",UP],AUTHORITY["EPSG","6360"]]]', + ), + ).toEqual({ + type: 'COMPD_CS', + name: 'unknown', + PROJCS: { + name: 'NAD83 / Texas North Central', + projName: 'Lambert_Conformal_Conic_2SP', + GEOGCS: { + name: 'NAD83', + DATUM: { + name: 'North_American_Datum_1983', + SPHEROID: { + name: 'GRS 1980', + a: 6378137, + rf: 298.257222101004, + AUTHORITY: { + EPSG: '7019', + }, + }, + AUTHORITY: { + EPSG: '6269', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '', + }, + }, + UNIT: { + name: 'degree', + convert: 0.0174532925199433, + AUTHORITY: { + EPSG: '9122', + }, + }, + AUTHORITY: { + EPSG: '4269', + }, + }, + PROJECTION: 'Lambert_Conformal_Conic_2SP', + latitude_of_origin: 31.6666666666667, + central_meridian: -98.5, + standard_parallel_1: 32.1333333333333, + standard_parallel_2: 33.9666666666667, + false_easting: 1968500, + false_northing: 6561666.66666667, + UNIT: { + name: 'US survey foot', + convert: 0.304800609601219, + AUTHORITY: { + EPSG: '9003', + }, + }, + AXIS: [ + ['Easting', 'EAST'], + ['Northing', 'NORTH'], + ], + AUTHORITY: { + EPSG: '32138', + }, + }, + VERT_CS: { + name: 'NAVD88 height (ftUS)', + VERT_DATUM: { + name: 'North American Vertical Datum 1988', + convert: 2005, + AUTHORITY: { + EPSG: '5103', + }, + }, + UNIT: { + name: 'US survey foot', + convert: 0.304800609601219, + AUTHORITY: { + EPSG: '9003', + }, + }, + AXIS: [['Up', 'UP']], + AUTHORITY: { + EPSG: '6360', + }, + }, + srsCode: 'unknown', + }); +}); + +test('parse fixture 5', () => { + expect( + parseWKTProjection( + 'PROJCS["WGS 84 / Antarctic Polar Stereographic",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Polar_Stereographic"],PARAMETER["latitude_of_origin",-71],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],AUTHORITY["EPSG","3031"],AXIS["Easting",UNKNOWN],AXIS["Northing",UNKNOWN]]', + ), + ).toEqual({ + type: 'PROJCS', + name: 'WGS 84 / Antarctic Polar Stereographic', + GEOGCS: { + name: 'WGS 84', + DATUM: { + name: 'WGS_1984', + SPHEROID: { + name: 'WGS 84', + a: 6378137, + rf: 298.257223563, + AUTHORITY: { + EPSG: '7030', + }, + }, + AUTHORITY: { + EPSG: '6326', + }, + }, + PRIMEM: { + name: 'Greenwich', + convert: 0, + AUTHORITY: { + EPSG: '8901', + }, + }, + UNIT: { + name: 'degree', + convert: 0.01745329251994328, + AUTHORITY: { + EPSG: '9122', + }, + }, + AUTHORITY: { + EPSG: '4326', + }, + }, + UNIT: { + name: 'metre', + convert: 1, + AUTHORITY: { + EPSG: '9001', + }, + }, + PROJECTION: 'Polar_Stereographic', + latitude_of_origin: -71, + central_meridian: 0, + scale_factor: 1, + false_easting: 0, + false_northing: 0, + AUTHORITY: { + EPSG: '3031', + }, + AXIS: [ + ['Easting', 'UNKNOWN'], + ['Northing', 'UNKNOWN'], + ], + projName: 'Polar_Stereographic', + axis: 'enu', + units: 'meter', + to_meter: 1, + datumCode: 'wgs84', + ellps: 'WGS 84', + a: 6378137, + rf: 298.257223563, + k0: 1, + x0: 0, + y0: 0, + long0: 0, + lat0: -1.5707963267948966, + srsCode: 'WGS 84 / Antarctic Polar Stereographic', + lat_ts: -1.239183768915974, + }); +}); diff --git a/tmp.ts b/tmp.ts index 17d73699..5a02429d 100644 --- a/tmp.ts +++ b/tmp.ts @@ -1,68 +1,236 @@ -function createDataView(): DataView { - // Calculate total byte size based on the sizes of each data type - const bufferSize = 1 // Uint8 = 1 byte - + 2 // Uint16 = 2 bytes - + 4 // Uint32 = 4 bytes - + 1 // Int8 = 1 byte - + 2 // Int16 = 2 bytes - + 4 // Int32 = 4 bytes - + 4 // Float32 = 4 bytes - + 8 // Float64 = 8 bytes - + 8 // BigUint64 = 8 bytes - + 8; // BigInt64 = 8 bytes - - // Create the buffer and wrap it in a DataView - const buffer = new ArrayBuffer(bufferSize); - const dv = new DataView(buffer); - - let offset = 0; - - // Set data in the DataView using the specified methods - - // Set Uint8 (1 byte) - dv.setUint8(offset, 255); - offset += 1; - - // Set Uint16 (2 bytes) - dv.setUint16(offset, 65535, true); // littleEndian - offset += 2; - - // Set Uint32 (4 bytes) - dv.setUint32(offset, 4294967295, true); // littleEndian - offset += 4; - - // Set Int8 (1 byte) - dv.setInt8(offset, -128); - offset += 1; - - // Set Int16 (2 bytes) - dv.setInt16(offset, -32768, true); // littleEndian - offset += 2; - - // Set Int32 (4 bytes) - dv.setInt32(offset, -2147483648, true); // littleEndian - offset += 4; - - // Set Float32 (4 bytes) - dv.setFloat32(offset, 3.14, true); // littleEndian - offset += 4; - - // Set Float64 (8 bytes) - dv.setFloat64(offset, 3.14159265359, true); // littleEndian - offset += 8; - - // Set BigUint64 (8 bytes) - dv.setBigUint64(offset, 12345678901234567890n, true); // littleEndian - offset += 8; - - // Set BigInt64 (8 bytes) - dv.setBigInt64(offset, -1234567890123456789n, true); // littleEndian - offset += 8; - - return dv; +// function createDataView(): DataView { +// // Calculate total byte size based on the sizes of each data type +// const bufferSize = 1 // Uint8 = 1 byte +// + 2 // Uint16 = 2 bytes +// + 4 // Uint32 = 4 bytes +// + 1 // Int8 = 1 byte +// + 2 // Int16 = 2 bytes +// + 4 // Int32 = 4 bytes +// + 4 // Float32 = 4 bytes +// + 8 // Float64 = 8 bytes +// + 8 // BigUint64 = 8 bytes +// + 8; // BigInt64 = 8 bytes + +// // Create the buffer and wrap it in a DataView +// const buffer = new ArrayBuffer(bufferSize); +// const dv = new DataView(buffer); + +// let offset = 0; + +// // Set data in the DataView using the specified methods + +// // Set Uint8 (1 byte) +// dv.setUint8(offset, 255); +// offset += 1; + +// // Set Uint16 (2 bytes) +// dv.setUint16(offset, 65535, true); // littleEndian +// offset += 2; + +// // Set Uint32 (4 bytes) +// dv.setUint32(offset, 4294967295, true); // littleEndian +// offset += 4; + +// // Set Int8 (1 byte) +// dv.setInt8(offset, -128); +// offset += 1; + +// // Set Int16 (2 bytes) +// dv.setInt16(offset, -32768, true); // littleEndian +// offset += 2; + +// // Set Int32 (4 bytes) +// dv.setInt32(offset, -2147483648, true); // littleEndian +// offset += 4; + +// // Set Float32 (4 bytes) +// dv.setFloat32(offset, 3.14, true); // littleEndian +// offset += 4; + +// // Set Float64 (8 bytes) +// dv.setFloat64(offset, 3.14159265359, true); // littleEndian +// offset += 8; + +// // Set BigUint64 (8 bytes) +// dv.setBigUint64(offset, 12345678901234567890n, true); // littleEndian +// offset += 8; + +// // Set BigInt64 (8 bytes) +// dv.setBigInt64(offset, -1234567890123456789n, true); // littleEndian +// offset += 8; + +// return dv; +// } + +// // Usage +// const dataView = createDataView(); +// console.log(dataView); +// Bun.write('./tests/readers/fixtures/dv.bin', dataView.buffer); + + + + +const wktStr = 'PROJCS["NZGD49 / New Zealand Map Grid",GEOGCS["NZGD49",DATUM["New_Zealand_Geodetic_Datum_1949",SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]],TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["New_Zealand_Map_Grid"],PARAMETER["latitude_of_origin",-41],PARAMETER["central_meridian",173],PARAMETER["false_easting",2510000],PARAMETER["false_northing",6023150],AUTHORITY["EPSG","27200"],AXIS["Easting",EAST],AXIS["Northing",NORTH]]' + +// PROJCS[ +// "NZGD49 / New Zealand Map Grid", +// GEOGCS[ +// "NZGD49", +// DATUM[ +// "New_Zealand_Geodetic_Datum_1949", +// SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]], +// TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993], +// AUTHORITY["EPSG","6272"] +// ], +// PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]], +// UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]], +// AUTHORITY["EPSG","4272"] +// ], +// UNIT["metre",1,AUTHORITY["EPSG","9001"]], +// PROJECTION["New_Zealand_Map_Grid"], +// PARAMETER["latitude_of_origin",-41], +// PARAMETER["central_meridian",173], +// PARAMETER["false_easting",2510000], +// PARAMETER["false_northing",6023150], +// AUTHORITY["EPSG","27200"], +// AXIS["Easting",EAST], +// AXIS["Northing",NORTH] +// ] + +const wktStr1 = 'PROJCS["NZGD49 / New Zealand Map Grid","stringCase"]' +const wktStr2 = 'PROJCS["NZGD49 / New Zealand Map Grid",TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],PARAMETER["central_meridian",173]]' +// const wktStr2 = 'PROJCS["NZGD49 / New Zealand Map Grid",TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993]]' + +const wktStr3 = 'PROJCS["A",B["C", D, 1[2, 3]], E["F", G, H]]' + +type WKTValue = undefined | string | { [key: string]: WKTValue } +type WKTObject = Record + +function parseWKT(wktStr: string): WKTObject { + const res: WKTObject = {}; + _parseWKTObject(wktStr, res); + return res; +} + +// always return the endBracketIndex if we hit it +function _parseWKTObject(wktStr: string, res: WKTObject): string { + // first get the object name and build the residual + while (wktStr.length) { + let commaIndex = wktStr.indexOf(','); + let startBracketIndex = wktStr.indexOf('['); + const endBracketIndex = wktStr.indexOf(']'); + if (commaIndex === -1) commaIndex = Infinity; + if (startBracketIndex === -1) startBracketIndex = Infinity; + if (commaIndex < Math.min(startBracketIndex, endBracketIndex)) { + // store the value + const key = wktStr.slice(0, commaIndex); + if (key.length > 0) res[cleanString(key)] = undefined; + wktStr = wktStr.slice(commaIndex + 1); + } else if (startBracketIndex < endBracketIndex) { + // store the object + const key = wktStr.slice(0, startBracketIndex); + const obj = res[cleanString(key)] = {}; + wktStr = _parseWKTObject(wktStr.slice(startBracketIndex + 1), obj); + } else { + // store the LAST value if it exists, be sure to increment past the bracket for this recursive call + if (endBracketIndex > 0) { + res[cleanString(wktStr.slice(0, endBracketIndex))] = undefined; + wktStr = wktStr.slice(endBracketIndex + 1); + } else { + wktStr = wktStr.slice(1); + } + return wktStr; + } + } + // hit the end + return wktStr; +} + +function cleanString(str: string): string { + return str + .trim() // Remove whitespace at the start and end + .replace(/^['"]|['"]$/g, '') // Remove single or double quotes from start and end + .replace(/\s+/g, ' '); // Replace multiple spaces with a single space +} + +// TRACKERS: offset, commaIndex, startBracketIndex, endBracketIndex + +// CASE A: hit the end (59.47]) +// CASE B: hit a comma (59.47,"-5.04",...etc) +// CASE C: hit a bracket/object (WORD[-5.04,..],...etc) + +// Value is either a string, an array of strings, or an object. +// const wktObj = parseWKT(wktStr1) +// console.log('RES 1', wktObj) + +// const wktObj2 = parseWKT(wktStr2) +// console.log('RES 2', wktObj2) + +// const wktObj3 = parseWKT(wktStr3) +// console.log('RES 3', wktObj3) + +// const wktObjFull = parseWKT(wktStr); +// console.log('RES FULL', wktObjFull) + +interface VectorPoint { + x: number; + y: number; + z?: number; +} + +type WKTAValue = VectorPoint | WKTAValue[] +type WKTArray = WKTAValue[] + +function parseWKTArray(wktStr: string): WKTArray { + const res: WKTArray = []; + _parseWKTArray(wktStr, res); + return res.length ? (res[0] as WKTArray) : res; +} + +// always return the endBracketIndex if we hit it +function _parseWKTArray(wktStr: string, res: WKTArray): string { + console.log('PARSE', wktStr); + // first get the array name and build the residual + while (wktStr.length) { + let commaIndex = wktStr.indexOf(','); + let startBracketIndex = wktStr.indexOf('('); + const endBracketIndex = wktStr.indexOf(')'); + if (commaIndex === -1) commaIndex = Infinity; + if (startBracketIndex === -1) startBracketIndex = Infinity; + if (commaIndex < Math.min(startBracketIndex, endBracketIndex)) { + console.log('CASE A'); + // store the value + const key = wktStr.slice(0, commaIndex); + if (key.length > 0) res.push(buildPoint(key)); + wktStr = wktStr.slice(commaIndex + 1); + } else if (startBracketIndex < endBracketIndex) { + console.log('CASE B') + // store the array + const array: WKTArray = []; + wktStr = _parseWKTArray(wktStr.slice(startBracketIndex + 1), array); + res.push(array); + } else { + console.log('CASE C', wktStr, endBracketIndex) + // store the LAST value if it exists, be sure to increment past the bracket for this recursive call + if (endBracketIndex > 0) { + res.push(buildPoint(wktStr.slice(0, endBracketIndex))); + wktStr = wktStr.slice(endBracketIndex + 1); + } else { + wktStr = wktStr.slice(1); + } + return wktStr; + } + } + // hit the end + return wktStr; +} + +function buildPoint(str: string): VectorPoint { + const [x, y, z] = cleanString(str).split(' '); + return { x: +x, y: +y, z: z ? +z : undefined }; } -// Usage -const dataView = createDataView(); -console.log(dataView); -Bun.write('./tests/readers/fixtures/dv.bin', dataView.buffer); +// const str = '(0 1)'; +const str = '(30 10, 10 30, 40 40)'; +// const str = '(((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))'; +console.log(parseWKTArray(str)); \ No newline at end of file