From 4b878b4d9ff8816de60557217037240ec8b40074 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Mon, 22 Nov 2021 10:59:53 +0100
Subject: [PATCH 01/16] Fixing test expectation for updates in
 pg_sphere.test.sql

---
 expected/init_test.out | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/expected/init_test.out b/expected/init_test.out
index 6d91697..01d73a2 100644
--- a/expected/init_test.out
+++ b/expected/init_test.out
@@ -33,5 +33,5 @@ psql:pg_sphere.test.sql:8569: NOTICE:  argument type pointkey is only a shell
 psql:pg_sphere.test.sql:8575: NOTICE:  argument type pointkey is only a shell
 psql:pg_sphere.test.sql:8581: NOTICE:  argument type pointkey is only a shell
 psql:pg_sphere.test.sql:8587: NOTICE:  argument type pointkey is only a shell
-psql:pg_sphere.test.sql:9152: NOTICE:  return type smoc is only a shell
-psql:pg_sphere.test.sql:9158: NOTICE:  argument type smoc is only a shell
+psql:pg_sphere.test.sql:9154: NOTICE:  return type smoc is only a shell
+psql:pg_sphere.test.sql:9160: NOTICE:  argument type smoc is only a shell

From 97080b5c18d4a7c89b11fb388b5944ebb1e09b29 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Mon, 22 Nov 2021 11:35:38 +0100
Subject: [PATCH 02/16] Adding documentation for the max_order function

---
 doc/functions.sgm | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/doc/functions.sgm b/doc/functions.sgm
index be89326..1318141 100644
--- a/doc/functions.sgm
+++ b/doc/functions.sgm
@@ -638,6 +638,34 @@
             </programlisting>
           </example>
 
+          <funcsynopsis>
+            <funcprototype>
+              <funcdef><function>max_order</function></funcdef>
+                <paramdef>smoc</paramdef>
+            </funcprototype>
+          </funcsynopsis>
+          <para>
+            Returns the maximal order of an smoc.
+          </para>
+          <para>
+          	The maximal order of a MOC can be higher than than the highest
+          	order appearing in the serialisation.  For instance, in the example
+          	below the full-sky coverage given at order 6 means that no patch
+          	larger than about 1 degree is not covered.  In the ASCII
+          	serialisation, give the maximum order with an with an empty cell
+          	list.
+          </para>
+
+          <example>
+            <title>Obtaining a MOC order</title>
+            <programlisting>
+<![CDATA[SELECT max_order(smoc('0/0-11 6/')) as order;]]>
+<![CDATA[    order]]>
+<![CDATA[-----------]]>
+<![CDATA[ 6 ]]>
+            </programlisting>
+          </example>
+
         </sect2>
         
     </sect1>

From 2bde2b191f2f7a53b4f0a4149abd832056fd86bb Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Wed, 8 Dec 2021 17:03:37 +0100
Subject: [PATCH 03/16] Adding support of casting of circles and polygons to
 mocs in contains and intersects constraints.

I'm also adding a little file on how to hack pg_sphere; yes, that's
mainly repeating stuff from the pg docs, but I guess it'll help
a few folks; it would have helped me.
---
 .gitignore                                    |   1 +
 HACKING                                       |  50 +++
 Makefile                                      |  17 +-
 doc/gen_moccast.py                            | 156 ++++++++
 doc/operators.sgm                             |  45 ++-
 expected/mocautocast.out                      | 209 ++++++++++
 ...e--1.2.0.sql.in => pg_sphere--1.2.1.sql.in |   0
 pg_sphere.control                             |   2 +-
 pgs_moc_geo_casts.sql.in                      | 374 ++++++++++++++++++
 sql/mocautocast.sql                           | 123 ++++++
 10 files changed, 969 insertions(+), 8 deletions(-)
 create mode 100644 HACKING
 create mode 100644 doc/gen_moccast.py
 create mode 100644 expected/mocautocast.out
 rename pg_sphere--1.2.0.sql.in => pg_sphere--1.2.1.sql.in (100%)
 create mode 100644 pgs_moc_geo_casts.sql.in
 create mode 100644 sql/mocautocast.sql

diff --git a/.gitignore b/.gitignore
index 4e384bf..7ab2e85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
 regression.out
 regression.diffs
 tags
+buildpod
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..0b86594
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,50 @@
+SQL definitions
+===============
+
+Long version:
+<https://www.postgresql.org/docs/current/extend-extensions.html>.
+
+If you're writing new features that require SQL support, pick some
+descriptive name; let's say my_new_op.  
+
+Put your new code into a file called pgs_my_new_op.sql.in.  The .in
+extension here usually indicates "it's for copying stuff together";
+usally, not much processing is done on such files.
+
+Then edit the Makefile.  The PGS_SQL variable contains a list of the
+SQL files eventually copied together, without the .in.  Add your new
+file there.
+
+You will also need to create an upgrade file.  In order to tell postgres
+to execute it, increase PGSPHERE_VERSION as appropriate.  As a
+consequence, you will have to::
+
+	git mv pg_sphere--<old version>.sql.in pg_sphere--<new version>.sql.in
+
+and also to update the version in pg_sphere.control.
+
+Then create a make rule:: 
+
+	pg_sphere--<old version>--<new version>.sql: pgs_my_new_op.sql.in
+		cat $^ > $@
+
+(of course, this will extend to having multiple sql.in files).
+
+Finally, add the target of that rule to the DATA_built variable.
+
+
+Regression tests
+================
+
+Regressions tests are as per
+<https://www.postgresql.org/docs/current/extend-pgxs.html>.
+
+In short, write queries executing your new features into a file
+sql/my_new_op.sql, and add "my_new_op" (without the extension or the
+directory name) to both REGRESS and TESTS in the Makefile.
+
+Then touch expected/my_new_op.out, run make test.  This will of course
+fail, because your tests hopefully will output something.  But then you
+can pick out the diff from
+/var/lib/postgresql/pgsphere/regression.diffs, have another critical
+look at it and generatoe your .out file from it.
diff --git a/Makefile b/Makefile
index fed246b..c3c5296 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PGSPHERE_VERSION = 1.2.0
+PGSPHERE_VERSION = 1.2.1
 
 # the base dir name may be changed depending on git clone command
 SRC_DIR = $(shell basename $(shell pwd))
@@ -17,18 +17,19 @@ DATA_built  = $(RELEASE_SQL) \
 			  pg_sphere--1.0_gavo--1.1.5beta0gavo.sql \
 			  pg_sphere--1.1.5beta0gavo--1.1.5beta2gavo.sql \
 			  pg_sphere--1.1.5beta2gavo--1.1.5beta4gavo.sql \
-			  pg_sphere--1.1.5beta4gavo--1.2.0.sql
+			  pg_sphere--1.1.5beta4gavo--1.2.0.sql \
+			  pg_sphere--1.2.0--1.2.1.sql
 
 DOCS        = README.pg_sphere COPYRIGHT.pg_sphere
 REGRESS     = init tables points euler circle line ellipse poly path box index \
 			  contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
-			  moc
+			  moc mocautocast
 
 REGRESS_9_5 = index_9.5 # experimental for spoint3
 
 TESTS       = init_test tables points euler circle line ellipse poly path box index \
 			  contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
-			  moc
+			  moc mocautocast
 
 ifndef CXXFLAGS
 # no support for CXXFLAGS in PGXS before v11
@@ -45,7 +46,8 @@ PGS_SQL     = pgs_types.sql pgs_point.sql pgs_euler.sql pgs_circle.sql \
    pgs_line.sql pgs_ellipse.sql pgs_polygon.sql pgs_path.sql \
    pgs_box.sql pgs_contains_ops.sql pgs_contains_ops_compat.sql \
    pgs_gist.sql gnomo.sql \
-   healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql
+   healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql \
+   pgs_moc_geo_casts.sql
 PGS_SQL_9_5 = pgs_9.5.sql # experimental for spoint3
 
 USE_PGXS = 1
@@ -154,7 +156,7 @@ ifeq ($(pg_version_9_5_plus),y)
 else
 endif
 
-# local stuff follows here, next will be "beta2"
+# local stuff follows here
 
 AUGMENT_GAVO_111 = $(AUGMENT_UNP_111) healpix.sql # for vanilla 1.1.1 users
 UPGRADE_GAVO_111 = $(UPGRADE_UNP_COMMON)
@@ -196,6 +198,9 @@ ifeq ($(has_parallel), n)
 	sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
 endif
 
+pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
+	cat $^ > $@
+
 # end of local stuff
 
 sscan.o : sparse.c
diff --git a/doc/gen_moccast.py b/doc/gen_moccast.py
new file mode 100644
index 0000000..49c816f
--- /dev/null
+++ b/doc/gen_moccast.py
@@ -0,0 +1,156 @@
+# A script to create the automatic casts for overlaps and intersects
+# between MOCs and spolys/scircles.
+#
+# This has originally been used to create pg_sphere--1.2.0--1.2.1.sql.
+# Before 1.2.1 is release, this can be fixed to improve that SQL.
+# After the 1.2.1 release, this is just documentation on how MOC
+# casts were generated that is perhaps just a bit more readable than
+# that bunch of SQL.
+
+import datetime
+import re
+import sys
+
+
+OVERLAP_DEFS = [
+    # func_stem, operator, commutator
+    ('subset', '<@', '@>'),
+    ('not_subset', '!<@', '!@>'),
+    ('superset', '@>', '<@'),
+    ('not_superset', '!@>', '!<@'),
+]
+
+INTERSECT_DEFS = [
+    # func_stem, operator, commutator
+    ('intersect', '&&', '&&'),
+    ('not_intersect', '!&&', '!&&'),
+]
+
+
+GEO_TYPES = ["scircle", "spoly"]
+
+OP_DEFS = OVERLAP_DEFS
+
+
+class Accum:
+    """an accumulator for our output.
+    """
+    def __init__(self):
+        self.parts = []
+
+    @property
+    def content(self):
+        return "".join(self.parts)
+
+    def write(self, s):
+        self.parts.append(s)
+
+    def writeln(self, *strings):
+        self.parts.append("\n".join(strings)+"\n")
+
+    def replace_last(self, subs):
+        """replaces the last non-whitespace char with the string subs.
+        """
+        for index, part in enumerate(reversed(self.parts)):
+            if part.strip():
+                break
+        else:
+            # nothing to replace
+            return
+        
+        index = -1-index
+        self.parts[index] = re.sub("[^\s](\s*)$", 
+            lambda mat: subs+mat.group(1),
+            self.parts[index])
+
+    def introduce_section(self, sec_name):
+        self.writeln()
+        self.writeln("-- #################################")
+        self.writeln(f"-- {sec_name}")
+
+
+def emit_drop_code(accum):
+    accum.introduce_section("Cleanup")
+
+    accum.writeln("DROP OPERATOR IF EXISTS")
+    for _, op, _ in OP_DEFS:
+        for geo_type in GEO_TYPES:
+            accum.writeln(f"    {op} (smoc, {geo_type}),")
+            accum.writeln(f"    {op} ({geo_type}, smoc),")
+    accum.replace_last(";")
+
+
+def make_negator(op):
+    if op.startswith("!"):
+        return op[1:]
+    else:
+        return "!"+op
+
+
+def emit_op_def(accum, operator, leftarg, rightarg, procedure, commutator):
+    accum.writeln(
+        f"CREATE OPERATOR {operator} (",
+        f"  LEFTARG    = {leftarg},",
+        f"  RIGHTARG   = {rightarg},",
+        f"  PROCEDURE  = {procedure},",
+        f"  COMMUTATOR = '{commutator}',",
+        f"  NEGATOR    = '{make_negator(operator)}',",
+        f"  RESTRICT   = contsel,",
+        f"  JOIN       = contjoinsel",
+        f");")
+
+
+def emit_op_and_func(accum, op_def):
+    func_stem, operator, commutator = op_def
+    for geo_type in GEO_TYPES:
+        func_name = f"{geo_type}_{func_stem}_smoc"
+        accum.writeln(
+            f"CREATE OR REPLACE FUNCTION {func_name}(",
+            f"  geo_arg {geo_type}, a_moc smoc) RETURNS BOOL AS $body$",
+            f"    SELECT smoc(max_order(a_moc), geo_arg) {operator} a_moc",
+            f"  $body$ LANGUAGE SQL STABLE;")
+        emit_op_def(accum, operator,
+            geo_type, "smoc",
+            func_name,
+            commutator)
+ 
+        accum.writeln()
+
+        func_name = f"smoc_{func_stem}_{geo_type}"
+        accum.writeln(
+            f"CREATE OR REPLACE FUNCTION {func_name}(",
+            f"  a_moc smoc, geo_arg {geo_type}) RETURNS BOOL AS $body$",
+            f"    SELECT a_moc {operator} smoc(max_order(a_moc), geo_arg)",
+            f"  $body$ LANGUAGE SQL STABLE;")
+        emit_op_def(accum, operator,
+            "smoc", geo_type,
+            func_name,
+            commutator)
+
+        accum.writeln()
+
+
+def main():
+    accum = Accum()
+
+    accum.writeln("-- MOC/geometry automatic casts.")
+    accum.writeln(f"-- Generated {datetime.date.today()} by {sys.argv[0]}.")
+    accum.writeln(f"-- Re-generation needs to be triggered manually.")
+    accum.writeln()
+    emit_drop_code(accum)
+  
+    accum.introduce_section(" smoc/geo OVERLAPS")
+    for op_def in OVERLAP_DEFS:
+        emit_op_and_func(accum, op_def)
+        accum.writeln()
+
+    accum.introduce_section(" smoc/geo INTERSECTS")
+    for op_def in INTERSECT_DEFS:
+        emit_op_and_func(accum, op_def)
+        accum.writeln()
+
+    print(accum.content)
+
+    
+if __name__=="__main__":
+    main()
diff --git a/doc/operators.sgm b/doc/operators.sgm
index fe22550..7162a01 100644
--- a/doc/operators.sgm
+++ b/doc/operators.sgm
@@ -231,10 +231,19 @@
             <para>
                 An overlap or contain operator does not exist for all
                 combinations of data types.
-                For instance, scircle <@ <type>spoint</type> is
+                For instance, scircle &lt;@ <type>spoint</type> is
                 useless because a spherical point can never contain a
                 spherical circle.
             </para>
+            <para>
+                When one of the arguments of such an operator is a MOC
+                and the other is an scircle or an spoly, the non-MOC argument
+                is converted to a MOC of the order of the maximum order of
+                the MOC.  When comparing against a MOC-valued column, it
+                is usually much faster to explicitly convert the geometry
+                using the smoc constructor, as the conversion will then
+                only happen once.
+            </para>
             <example>
               <title>Is the left circle contained by the right circle?</title>
               <programlisting>
@@ -255,6 +264,40 @@
 <![CDATA[(1 row)]]>
               </programlisting>
             </example>
+
+            <example>
+              <title>Overlaps between a circle and a moc</title>
+              <programlisting>
+<![CDATA[sql> SELECT scircle '<(37d, 5d), 0.25d>' <@ smoc('4/1117') AS test ;]]>
+<![CDATA[ test]]>
+<![CDATA[------]]>
+<![CDATA[ f]]>
+<![CDATA[(1 row)]]>
+              </programlisting>
+            </example>
+
+            <example>
+              <title>Overlaps between a circle and a moc with explicit order</title>
+              <programlisting>
+<![CDATA[sql> SELECT scircle '<(37d, 5d), 0.25d>' <@ smoc('4/1117 5/') AS test ;]]>
+<![CDATA[ test]]>
+<![CDATA[------]]>
+<![CDATA[ t]]>
+<![CDATA[(1 row)]]>
+              </programlisting>
+            </example>
+
+            <example>
+              <title>Overlaps between a circle and a moc with explicit cast (normally faster)</title>
+              <programlisting>
+<![CDATA[sql> SELECT smoc(5, scircle '<(37d, 5d), 0.25d>') <@ smoc('4/1117 5/') AS test ;]]>
+<![CDATA[ test]]>
+<![CDATA[------]]>
+<![CDATA[ t]]>
+<![CDATA[(1 row)]]>
+              </programlisting>
+            </example>
+
         </sect2>
 
         <sect2 id="op.cross">
diff --git a/expected/mocautocast.out b/expected/mocautocast.out
new file mode 100644
index 0000000..9be0ffc
--- /dev/null
+++ b/expected/mocautocast.out
@@ -0,0 +1,209 @@
+-- These are MOCs generated by
+-- SELECT smoc(mocorder, scircle '<(27d, -43d), 0.1d>');
+-- intended to check automatic casts of other geometries.
+CREATE TABLE varorders (
+	mocorder smallint,
+	geo smoc);
+INSERT INTO varorders (mocorder, geo) VALUES
+	(1, smoc('1/32 34 1/')),
+	(-3, smoc('1/32 34 3/')),
+	(3, smoc('3/547-548 550 553 3/')),
+	(7, smoc('7/140857-140860 140862 140944-140945 7/')),
+	(-7, smoc('1/32 34 7/'));
+-- OVERLAPS smoc/scircle
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsupc
+FROM varorders 
+WHERE geo @> scircle '<(30d, -43d), 1d>';
+ gsupc 
+-------
+ -7/-3
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsupc
+FROM varorders 
+WHERE geo !@> scircle '<(30d, -43d), 1d>';
+  gnsupc  
+----------
+  1/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) csubg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' <@ geo;
+ csubg 
+-------
+ -7/-3
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnsubg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' !<@ geo;
+  cnsubg  
+----------
+  1/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsubc
+FROM varorders 
+WHERE geo <@ scircle '<(30d, -43d), 1d>';
+ gsubc 
+-------
+  1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsubc
+FROM varorders 
+WHERE geo !<@ scircle '<(30d, -43d), 1d>';
+   gnsubc    
+-------------
+ -7/-3/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) csupg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' @> geo;
+ csupg 
+-------
+  1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnsupg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' !@> geo;
+   cnsupg    
+-------------
+ -7/-3/ 3/ 7
+(1 row)
+
+-- OVERLAPS smoc/spoly
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsupp
+FROM varorders 
+WHERE geo @> spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+ gsupp 
+-------
+ -7/-3
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsupp
+FROM varorders 
+WHERE geo !@> spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+  gnsupp  
+----------
+  1/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) psubg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') <@ geo;
+ psubg 
+-------
+ -7/-3
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) pnsubg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') !<@ geo;
+  pnsubg  
+----------
+  1/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsubp
+FROM varorders 
+WHERE geo <@ spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+ gsubp 
+-------
+  1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsubp
+FROM varorders 
+WHERE geo !<@ spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+   gnsubp    
+-------------
+ -7/-3/ 3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) psupg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') @> geo;
+ psupg 
+-------
+  1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) pnsupg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') !@> geo;
+   pnsupg    
+-------------
+ -7/-3/ 3/ 7
+(1 row)
+
+-- INTERSECTS smoc/scircle
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgim
+FROM varorders 
+WHERE scircle '<(45d, -40d), 1d>' && geo;
+   cgim   
+----------
+ -7/-3/ 1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgnim
+FROM varorders 
+WHERE scircle '<(45d, -40d), 1d>' !&& geo;
+ cgnim 
+-------
+  3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cmig
+FROM varorders 
+WHERE geo && scircle '<(45d, -40d), 1d>';
+   cmig   
+----------
+ -7/-3/ 1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnim
+FROM varorders 
+WHERE geo !&& scircle '<(45d, -40d), 1d>';
+ cnim  
+-------
+  3/ 7
+(1 row)
+
+-- INTERSECTS smoc/spoly
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgim
+FROM varorders 
+WHERE spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}' && geo;
+   cgim   
+----------
+ -7/-3/ 1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgnim
+FROM varorders 
+WHERE spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}' !&& geo;
+ cgnim 
+-------
+  3/ 7
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cmig
+FROM varorders 
+WHERE geo && spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}';
+   cmig   
+----------
+ -7/-3/ 1
+(1 row)
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnim
+FROM varorders 
+WHERE geo !&& spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}';
+ cnim  
+-------
+  3/ 7
+(1 row)
+
+DROP TABLE varorders;
diff --git a/pg_sphere--1.2.0.sql.in b/pg_sphere--1.2.1.sql.in
similarity index 100%
rename from pg_sphere--1.2.0.sql.in
rename to pg_sphere--1.2.1.sql.in
diff --git a/pg_sphere.control b/pg_sphere.control
index eacc07b..2b64f2f 100644
--- a/pg_sphere.control
+++ b/pg_sphere.control
@@ -1,5 +1,5 @@
 # pg_sphere extension
 comment = 'spherical objects with useful functions, operators and index support'
-default_version = '1.2.0'
+default_version = '1.2.1'
 module_pathname = '$libdir/pg_sphere'
 relocatable = true
diff --git a/pgs_moc_geo_casts.sql.in b/pgs_moc_geo_casts.sql.in
new file mode 100644
index 0000000..025cf04
--- /dev/null
+++ b/pgs_moc_geo_casts.sql.in
@@ -0,0 +1,374 @@
+-- MOC/geometry automatic casts.
+-- Generated 2021-12-08 by gen_moccast.py.
+-- You probably do not want to re-generate this; fixes need to be updates
+-- on the SQL level.
+
+
+-- #################################
+-- Cleanup
+-- DROP OPERATOR IF EXISTS
+--     <@ (smoc, scircle),
+--     <@ (scircle, smoc),
+--     <@ (smoc, spoly),
+--     <@ (spoly, smoc),
+--     !<@ (smoc, scircle),
+--     !<@ (scircle, smoc),
+--     !<@ (smoc, spoly),
+--     !<@ (spoly, smoc),
+--     @> (smoc, scircle),
+--     @> (scircle, smoc),
+--     @> (smoc, spoly),
+--     @> (spoly, smoc),
+--     !@> (smoc, scircle),
+--     !@> (scircle, smoc),
+--    !@> (smoc, spoly),
+--    !@> (spoly, smoc);
+
+-- #################################
+--  smoc/geo OVERLAPS
+CREATE OR REPLACE FUNCTION scircle_subset_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) <@ a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR <@ (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_subset_smoc,
+  COMMUTATOR = '@>',
+  NEGATOR    = '!<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_subset_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc <@ smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR <@ (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_subset_scircle,
+  COMMUTATOR = '@>',
+  NEGATOR    = '!<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_subset_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) <@ a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR <@ (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_subset_smoc,
+  COMMUTATOR = '@>',
+  NEGATOR    = '!<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_subset_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc <@ smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR <@ (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_subset_spoly,
+  COMMUTATOR = '@>',
+  NEGATOR    = '!<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+CREATE OR REPLACE FUNCTION scircle_not_subset_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !<@ a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !<@ (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_not_subset_smoc,
+  COMMUTATOR = '!@>',
+  NEGATOR    = '<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_subset_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc !<@ smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !<@ (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_not_subset_scircle,
+  COMMUTATOR = '!@>',
+  NEGATOR    = '<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_not_subset_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !<@ a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !<@ (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_not_subset_smoc,
+  COMMUTATOR = '!@>',
+  NEGATOR    = '<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_subset_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc !<@ smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !<@ (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_not_subset_spoly,
+  COMMUTATOR = '!@>',
+  NEGATOR    = '<@',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+CREATE OR REPLACE FUNCTION scircle_superset_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) @> a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR @> (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_superset_smoc,
+  COMMUTATOR = '<@',
+  NEGATOR    = '!@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_superset_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc @> smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR @> (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_superset_scircle,
+  COMMUTATOR = '<@',
+  NEGATOR    = '!@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_superset_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) @> a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR @> (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_superset_smoc,
+  COMMUTATOR = '<@',
+  NEGATOR    = '!@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_superset_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc @> smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR @> (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_superset_spoly,
+  COMMUTATOR = '<@',
+  NEGATOR    = '!@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+CREATE OR REPLACE FUNCTION scircle_not_superset_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !@> a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !@> (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_not_superset_smoc,
+  COMMUTATOR = '!<@',
+  NEGATOR    = '@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_superset_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc !@> smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !@> (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_not_superset_scircle,
+  COMMUTATOR = '!<@',
+  NEGATOR    = '@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_not_superset_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !@> a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !@> (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_not_superset_smoc,
+  COMMUTATOR = '!<@',
+  NEGATOR    = '@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_superset_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc !@> smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !@> (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_not_superset_spoly,
+  COMMUTATOR = '!<@',
+  NEGATOR    = '@>',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+
+-- #################################
+--  smoc/geo INTERSECTS
+CREATE OR REPLACE FUNCTION scircle_intersect_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) && a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR && (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_intersect_smoc,
+  COMMUTATOR = '&&',
+  NEGATOR    = '!&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_intersect_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc && smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR && (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_intersect_scircle,
+  COMMUTATOR = '&&',
+  NEGATOR    = '!&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_intersect_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) && a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR && (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_intersect_smoc,
+  COMMUTATOR = '&&',
+  NEGATOR    = '!&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_intersect_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc && smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR && (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_intersect_spoly,
+  COMMUTATOR = '&&',
+  NEGATOR    = '!&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+CREATE OR REPLACE FUNCTION scircle_not_intersect_smoc(
+  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !&& a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !&& (
+  LEFTARG    = scircle,
+  RIGHTARG   = smoc,
+  PROCEDURE  = scircle_not_intersect_smoc,
+  COMMUTATOR = '!&&',
+  NEGATOR    = '&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_intersect_scircle(
+  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
+    SELECT a_moc !&& smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !&& (
+  LEFTARG    = smoc,
+  RIGHTARG   = scircle,
+  PROCEDURE  = smoc_not_intersect_scircle,
+  COMMUTATOR = '!&&',
+  NEGATOR    = '&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION spoly_not_intersect_smoc(
+  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
+    SELECT smoc(max_order(a_moc), geo_arg) !&& a_moc
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !&& (
+  LEFTARG    = spoly,
+  RIGHTARG   = smoc,
+  PROCEDURE  = spoly_not_intersect_smoc,
+  COMMUTATOR = '!&&',
+  NEGATOR    = '&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+CREATE OR REPLACE FUNCTION smoc_not_intersect_spoly(
+  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
+    SELECT a_moc !&& smoc(max_order(a_moc), geo_arg)
+  $body$ LANGUAGE SQL STABLE;
+CREATE OPERATOR !&& (
+  LEFTARG    = smoc,
+  RIGHTARG   = spoly,
+  PROCEDURE  = smoc_not_intersect_spoly,
+  COMMUTATOR = '!&&',
+  NEGATOR    = '&&',
+  RESTRICT   = contsel,
+  JOIN       = contjoinsel
+);
+
+
+
diff --git a/sql/mocautocast.sql b/sql/mocautocast.sql
new file mode 100644
index 0000000..165f25f
--- /dev/null
+++ b/sql/mocautocast.sql
@@ -0,0 +1,123 @@
+-- These are MOCs generated by
+-- SELECT smoc(mocorder, scircle '<(27d, -43d), 0.1d>');
+-- intended to check automatic casts of other geometries.
+
+CREATE TABLE varorders (
+	mocorder smallint,
+	geo smoc);
+INSERT INTO varorders (mocorder, geo) VALUES
+	(1, smoc('1/32 34 1/')),
+	(-3, smoc('1/32 34 3/')),
+	(3, smoc('3/547-548 550 553 3/')),
+	(7, smoc('7/140857-140860 140862 140944-140945 7/')),
+	(-7, smoc('1/32 34 7/'));
+
+-- OVERLAPS smoc/scircle
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsupc
+FROM varorders 
+WHERE geo @> scircle '<(30d, -43d), 1d>';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsupc
+FROM varorders 
+WHERE geo !@> scircle '<(30d, -43d), 1d>';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) csubg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' <@ geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnsubg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' !<@ geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsubc
+FROM varorders 
+WHERE geo <@ scircle '<(30d, -43d), 1d>';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsubc
+FROM varorders 
+WHERE geo !<@ scircle '<(30d, -43d), 1d>';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) csupg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' @> geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnsupg
+FROM varorders 
+WHERE scircle '<(30d, -43d), 1d>' !@> geo;
+
+
+-- OVERLAPS smoc/spoly
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsupp
+FROM varorders 
+WHERE geo @> spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsupp
+FROM varorders 
+WHERE geo !@> spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) psubg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') <@ geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) pnsubg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') !<@ geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gsubp
+FROM varorders 
+WHERE geo <@ spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) gnsubp
+FROM varorders 
+WHERE geo !<@ spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}');
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) psupg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') @> geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) pnsupg
+FROM varorders 
+WHERE spoly('{(26d,-42d), (26d,-41d), (27d,-41d)}') !@> geo;
+
+
+-- INTERSECTS smoc/scircle
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgim
+FROM varorders 
+WHERE scircle '<(45d, -40d), 1d>' && geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgnim
+FROM varorders 
+WHERE scircle '<(45d, -40d), 1d>' !&& geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cmig
+FROM varorders 
+WHERE geo && scircle '<(45d, -40d), 1d>';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnim
+FROM varorders 
+WHERE geo !&& scircle '<(45d, -40d), 1d>';
+
+
+-- INTERSECTS smoc/spoly
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgim
+FROM varorders 
+WHERE spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}' && geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cgnim
+FROM varorders 
+WHERE spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}' !&& geo;
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cmig
+FROM varorders 
+WHERE geo && spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}';
+
+SELECT string_agg(to_char(mocorder, '9'), '/' ORDER BY mocorder) cnim
+FROM varorders 
+WHERE geo !&& spoly '{(51.1d, -50.1d), (45.8d, -47.5), (46.7d, -43.7d)}';
+
+
+DROP TABLE varorders;

From 9420a22c0c5943670f18a5d0708ed95f307d64bc Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Tue, 14 Dec 2021 11:16:27 +0100
Subject: [PATCH 04/16] Enabling operator dropping in the moc-vs-geometry SQL.

I hadn't done this before because the warnings spoiled the regression
expectation.  I'm now shutting those up for while I'm doing the dropping.
---
 pgs_moc_geo_casts.sql.in | 37 ++++++++++++++++++++-----------------
 1 file changed, 20 insertions(+), 17 deletions(-)

diff --git a/pgs_moc_geo_casts.sql.in b/pgs_moc_geo_casts.sql.in
index 025cf04..3ace187 100644
--- a/pgs_moc_geo_casts.sql.in
+++ b/pgs_moc_geo_casts.sql.in
@@ -6,23 +6,26 @@
 
 -- #################################
 -- Cleanup
--- DROP OPERATOR IF EXISTS
---     <@ (smoc, scircle),
---     <@ (scircle, smoc),
---     <@ (smoc, spoly),
---     <@ (spoly, smoc),
---     !<@ (smoc, scircle),
---     !<@ (scircle, smoc),
---     !<@ (smoc, spoly),
---     !<@ (spoly, smoc),
---     @> (smoc, scircle),
---     @> (scircle, smoc),
---     @> (smoc, spoly),
---     @> (spoly, smoc),
---     !@> (smoc, scircle),
---     !@> (scircle, smoc),
---    !@> (smoc, spoly),
---    !@> (spoly, smoc);
+
+set client_min_messages = 'warning';
+DROP OPERATOR IF EXISTS
+    <@ (smoc, scircle),
+    <@ (scircle, smoc),
+    <@ (smoc, spoly),
+    <@ (spoly, smoc),
+    !<@ (smoc, scircle),
+    !<@ (scircle, smoc),
+    !<@ (smoc, spoly),
+    !<@ (spoly, smoc),
+    @> (smoc, scircle),
+    @> (scircle, smoc),
+    @> (smoc, spoly),
+    @> (spoly, smoc),
+    !@> (smoc, scircle),
+    !@> (scircle, smoc),
+    !@> (smoc, spoly),
+    !@> (spoly, smoc);
+reset client_min_messages;
 
 -- #################################
 --  smoc/geo OVERLAPS

From 5e7ab4e313ee3af507093eaee9a9db64fb9c2386 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Mon, 5 Sep 2022 16:30:49 +0200
Subject: [PATCH 05/16] Attempting to fix upgrade script for pg 9.x.

---
 Makefile | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Makefile b/Makefile
index c3c5296..6387ba7 100644
--- a/Makefile
+++ b/Makefile
@@ -200,6 +200,9 @@ endif
 
 pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
 	cat $^ > $@
+ifeq ($(has_parallel), n)
+	sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
+endif
 
 # end of local stuff
 

From 2512fc856ff52316b81d86aa356a8e3643c187c9 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 30 May 2023 17:14:41 +0300
Subject: [PATCH 06/16] Added .deps filter in .gitignore

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 7ab2e85..ff5e881 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ regression.out
 regression.diffs
 tags
 buildpod
+.deps

From afa256ab97699b2765de3ea7ae7c1639508118a4 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 30 May 2023 17:45:47 +0300
Subject: [PATCH 07/16] Added CONTRIBUTING.md

---
 CONTRIBUTING.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d418258
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,20 @@
+First off, thanks for taking the time to contribute! ❤️
+
+All types of contributions are encouraged and valued. See the Table of Contents for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions.
+
+# Table of Contents
+- [Project Scope](#project-scope)
+- [Development Plans](#development-plans)
+
+# Project Scope
+- Types.
+- Operations on types (creating, searching, sorting, set operations and others).
+- Indexing structures and algorithms.
+
+# Development Plans
+- Move all source files into src folder.
+- Implement optional compilation with HEALPIX and SMOC.
+- Use pgindent to format sources.
+- Check compilation with newest postgrespro versions.
+- Add more tests including performance tests.
+- Add more documentation.

From ef881cd23df48647e97b44756aedd35e9aee4439 Mon Sep 17 00:00:00 2001
From: Vitaly <vitprof@gmail.com>
Date: Fri, 2 Jun 2023 11:05:08 +0300
Subject: [PATCH 08/16] Update README.pg_sphere

Text polishing.
---
 README.pg_sphere | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/README.pg_sphere b/README.pg_sphere
index 518b3eb..ebb104a 100644
--- a/README.pg_sphere
+++ b/README.pg_sphere
@@ -1,8 +1,18 @@
+pgSphere is a PostgreSQL extension for spherical geometry.
+
+It provides:
+
+* New data types (points, spherical polygons, paths, circles, ellipses, coordinate ranges)
+* Input and output of data in various formats
+* Membership, overlap, and other operators
+* Circumference and area of objects
+* Object rotation by Euler angles
+* Indexing of spherical data types
+
 This is an R-Tree implementation using GiST for spherical objects
 like spherical points and spherical circles with 
 useful functions and operators.
 
-
 NOTICE:
      This version will work only with postgresql version 9.1 and above.
 

From 2cfc44d8da3053ae00c707aa1588f1e602530c96 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Thu, 14 Apr 2022 13:04:28 +0200
Subject: [PATCH 09/16] + Basic proper motion propagation

This bumps version to 1.2.1, too.
---
 Makefile               |  14 +--
 epochprop.c            | 199 +++++++++++++++++++++++++++++++++++++++++
 epochprop.h            |  26 ++++++
 expected/epochprop.out | 107 ++++++++++++++++++++++
 pgs_epochprop.sql.in   |  41 +++++++++
 sql/epochprop.sql      |  78 ++++++++++++++++
 vector3d.c             |   8 ++
 vector3d.h             |   6 ++
 8 files changed, 474 insertions(+), 5 deletions(-)
 create mode 100644 epochprop.c
 create mode 100644 epochprop.h
 create mode 100644 expected/epochprop.out
 create mode 100644 pgs_epochprop.sql.in
 create mode 100644 sql/epochprop.sql

diff --git a/Makefile b/Makefile
index 3feabbd..5faa5f4 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,8 @@ MODULE_big = pg_sphere
 OBJS       = sscan.o sparse.o sbuffer.o vector3d.o point.o \
              euler.o circle.o line.o ellipse.o polygon.o \
              path.o box.o output.o gq_cache.o gist.o key.o \
-             gnomo.o healpix.o moc.o process_moc.o healpix_bare/healpix_bare.o
+             gnomo.o healpix.o moc.o process_moc.o healpix_bare/healpix_bare.o \
+             epochprop.o
 
 EXTENSION   = pg_sphere
 RELEASE_SQL = $(EXTENSION)--$(PGSPHERE_VERSION).sql
@@ -23,13 +24,13 @@ DATA_built  = $(RELEASE_SQL) \
 DOCS        = README.pg_sphere COPYRIGHT.pg_sphere
 REGRESS     = init tables points euler circle line ellipse poly path box index \
 			  contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
-			  moc mocautocast
+			  moc mocautocast epochprop
 
 REGRESS_9_5 = index_9.5 # experimental for spoint3
 
 TESTS       = init_test tables points euler circle line ellipse poly path box index \
 			  contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
-			  moc mocautocast
+			  moc mocautocast epochprop
 
 ifndef CXXFLAGS
 # no support for CXXFLAGS in PGXS before v11
@@ -39,7 +40,7 @@ endif
 
 EXTRA_CLEAN = $(PGS_SQL) pg_sphere.test.sql
 
-CRUSH_TESTS = init_extended circle_extended 
+CRUSH_TESTS = init_extended circle_extended
 
 # order of sql files is important
 PGS_SQL     = pgs_types.sql pgs_point.sql pgs_euler.sql pgs_circle.sql \
@@ -47,7 +48,7 @@ PGS_SQL     = pgs_types.sql pgs_point.sql pgs_euler.sql pgs_circle.sql \
    pgs_box.sql pgs_contains_ops.sql pgs_contains_ops_compat.sql \
    pgs_gist.sql gnomo.sql \
    healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql \
-   pgs_moc_geo_casts.sql
+   pgs_moc_geo_casts.sql pgs_epochprop.sql
 PGS_SQL_9_5 = pgs_9.5.sql # experimental for spoint3
 
 USE_PGXS = 1
@@ -197,6 +198,9 @@ pg_sphere--1.1.5beta4gavo--1.2.0.sql: pgs_moc_ops.sql.in
 	cat $^ > $@
 ifeq ($(has_parallel), n)
 	sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
+
+pg_sphere--1.2.0-1.2.1.sql: pgs_epochprop.sql.in
+	cat upgrade_scripts/$@.in $^ > $@
 endif
 
 pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
diff --git a/epochprop.c b/epochprop.c
new file mode 100644
index 0000000..116dc0b
--- /dev/null
+++ b/epochprop.c
@@ -0,0 +1,199 @@
+/* Handling of (conventional) proper motions.
+
+This code is largely based on a FORTRAN function written by Lennart Lindegren
+(Lund Obs) in 1995 that implements the procedure described in The Hipparcos
+and Tycho Catalogues (ESA SP-1200), Volume 1, Section 1.5.5, 'Epoch
+Transformation: Rigorous Treatment'; cf.
+<https://www.cosmos.esa.int/documents/532822/552851/vol1_all.pdf/99adf6e3-6893-4824-8fc2-8d3c9cbba2b5>.
+*/
+
+#include <math.h>
+#include <pgs_util.h>
+
+#include "point.h"
+#include "epochprop.h"
+#include "vector3d.h"
+
+PG_FUNCTION_INFO_V1(epoch_prop);
+
+/* Astronomical unit in kilometers */
+#define AU 1.495978701e8
+
+/* Julian year in seconds */
+#define J_YEAR (365.25*86400)
+
+/* A_nu as per ESA/SP-1200 */
+#define A_NU (AU/(J_YEAR))
+
+/* Following SOFA, we use 1e-7 arcsec as minimal parallax 
+	("celestial sphere"); parallax=0 exactly means "infinite distance", which
+	leads to all sorts for problems; our parallaxes come in in mas, so: */
+#define PX_MIN 1e-7*1000
+
+/* propagate an object at a phase vector over a time difference of delta_t,
+stuffing an updated phase vector in result.
+
+This does not propagate errors.
+*/
+static void propagate_phasevec(
+	const phasevec *pv,
+	const double delta_t,
+	phasevec *result) {
+
+	double distance_factor, mu0abs, zeta0, parallax;
+
+	Vector3D p0, r0, q0;
+	Vector3D mu0, pprime, qprime, mu, muprime, u, uprime;
+
+	/* for very small or null parallaxes, our algorithm breaks; avoid that
+		and, if we did emergency measures, do not talk about parallax and
+		radial velocity in the output */
+	if (pv->parallax_valid) {
+		parallax = pv->parallax;
+	} else {
+		parallax = PX_MIN;
+	}
+	result->parallax_valid = pv->parallax_valid;
+
+	/* compute the normal triad as Vector3D-s, eq. (1.2.15)*/
+	spoint_vector3d(&r0, &(pv->pos));
+
+	p0.x = -sin(pv->pos.lng);
+	p0.y = cos(pv->pos.lng); 
+	p0.z = 0;
+
+	q0.x = -sin(pv->pos.lat) * cos(pv->pos.lng);
+	q0.y = -sin(pv->pos.lat) * sin(pv->pos.lng); 
+	q0.z = cos(pv->pos.lat);
+
+	/* the original proper motion vector */
+	mu0.x = mu0.y = mu0.z = 0;
+	vector3d_addwithscalar(&mu0, pv->pm[0], &p0);
+	vector3d_addwithscalar(&mu0, pv->pm[1], &q0);
+	mu0abs = vector3d_length(&mu0);
+
+	/* radial velocity in mas/yr ("change of parallax per year").  eq. (1.5.24)
+		We're transforming this to rad/yr so the units work out below */
+	zeta0 = (pv->rv * parallax / A_NU) / 3.6e6 / RADIANS;
+	/* distance factor eq. (1.5.25) */
+	distance_factor = 1/sqrt(1
+		+ 2 * zeta0 * delta_t
+		+ (mu0abs * mu0abs + zeta0 * zeta0) * delta_t * delta_t);
+
+	/* the propagated proper motion vector, eq. (1.5.28) */
+	muprime.x = muprime.y = muprime.z = 0;
+	vector3d_addwithscalar(&muprime, (1 + zeta0 * delta_t), &mu0);
+	vector3d_addwithscalar(&muprime, -mu0abs * mu0abs * delta_t, &r0);
+	mu.x = mu.y = mu.z = 0;
+	vector3d_addwithscalar(&mu, pow(distance_factor, 3), &muprime);
+
+	/* parallax, eq. (1.5.27) */
+	result->parallax = distance_factor*parallax;
+	/* zeta/rv, eq. (1.5.29); go back from rad to mas, too */
+	result->rv = (zeta0 + (mu0abs * mu0abs + zeta0 * zeta0) * delta_t)
+		* distance_factor * distance_factor
+		* 3.6e6 * RADIANS
+		* A_NU / result->parallax;
+
+	/* propagated position, eq. (1.5.26) */
+	uprime.x = uprime.y = uprime.z = 0;
+	vector3d_addwithscalar(&uprime, (1 + zeta0 * delta_t), &r0);
+	vector3d_addwithscalar(&uprime, delta_t, &mu0);
+	u.x = u.y = u.z = 0;
+	vector3d_addwithscalar(&u, distance_factor, &uprime);
+	vector3d_spoint(&(result->pos), &u);
+
+	/* compute a new triad for the propagated position, eq (1.5.15) */
+	pprime.x = -sin(result->pos.lng);
+	pprime.y = cos(result->pos.lng);
+	pprime.z = 0;
+	qprime.x = -sin(result->pos.lat) * cos(result->pos.lng);
+	qprime.y = -sin(result->pos.lat) * sin(result->pos.lng);
+	qprime.z = cos(result->pos.lat);
+
+	/* use it to compute the proper motions, eq. (1.5.32) */
+	result->pm[0] = vector3d_scalar(&pprime, &mu);
+	result->pm[1] = vector3d_scalar(&qprime, &mu);
+}
+
+
+/* 
+	Propagate a position with proper motions and optionally parallax
+	and radial velocity.
+
+	Arguments: pos0 (spoint), pm_long, pm_lat (in rad/yr)
+	par (parallax, mas), rv (in km/s), delta_t (in years) 
+
+	This returns a 6-array of lat, long (in rad), parallax (in mas)
+	pmlat, pmlong (in rad/yr), rv (in km/s).
+*/
+Datum 
+epoch_prop(PG_FUNCTION_ARGS) {
+	double delta_t;
+	phasevec input, output;
+	ArrayType *result;
+	Datum retvals[6];
+
+	if (PG_ARGISNULL(0)) {
+		ereport(ERROR, 
+			(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				errmsg("NULL position not supported in epoch propagation"))); }
+	memcpy(&(input.pos), (void*)PG_GETARG_POINTER(0), sizeof(SPoint));
+	if (PG_ARGISNULL(1)) {
+		input.parallax = 0;
+	} else {
+		input.parallax = PG_GETARG_FLOAT8(1);
+	}
+	input.parallax_valid = fabs(input.parallax) > PX_MIN;
+	
+	if (PG_ARGISNULL(2)) {
+		input.pm[0] = 0;
+	} else {
+		input.pm[0] = PG_GETARG_FLOAT8(2);
+	}
+
+	if (PG_ARGISNULL(3)) {
+		input.pm[1] = 0;
+	} else {
+		input.pm[1] = PG_GETARG_FLOAT8(3);
+	}
+
+	if (PG_ARGISNULL(4)) {
+		input.rv = 0;
+	} else {
+		input.rv = PG_GETARG_FLOAT8(4);
+	}
+
+	delta_t = PG_GETARG_FLOAT8(5);
+
+	propagate_phasevec(&input, delta_t, &output);
+
+	/* change to internal units: rad, rad/yr, mas, and km/s */
+	retvals[0] = Float8GetDatum(output.pos.lng);
+	retvals[1] = Float8GetDatum(output.pos.lat);
+	retvals[2] = Float8GetDatum(output.parallax);
+	retvals[3] = Float8GetDatum(output.pm[0]);
+	retvals[4] = Float8GetDatum(output.pm[1]);
+	retvals[5] = Float8GetDatum(output.rv);
+
+	{
+		bool isnull[6] = {0, 0, 0, 0, 0, 0};
+		int lower_bounds[1] = {1};
+		int dims[1] = {6};
+#ifdef USE_FLOAT8_BYVAL
+		bool embyval = true;
+#else
+		bool embyval = false;
+#endif
+
+		if (! output.parallax_valid) {
+			/* invalidate parallax and rv */
+			isnull[2] = 1;
+			isnull[5] = 1;
+		}
+
+		result = construct_md_array(retvals, isnull, 1, dims, lower_bounds,
+			FLOAT8OID, sizeof(float8), embyval, 'd');
+	}
+	PG_RETURN_ARRAYTYPE_P(result);
+}
diff --git a/epochprop.h b/epochprop.h
new file mode 100644
index 0000000..9e1b87e
--- /dev/null
+++ b/epochprop.h
@@ -0,0 +1,26 @@
+/* Definitions for handling proper motions */
+
+#include <postgres.h>
+
+
+Datum epoch_prop(PG_FUNCTION_ARGS);
+
+
+/* a cartesian point; this is like geo_decl's point, but you can't
+have both geo_decls and pg_sphere right now (both define a type Point,
+not to mention they have different ideas on EPSILON */
+typedef struct s_cpoint {
+	double x, y;
+} CPoint;
+
+typedef struct s_phasevec {
+	SPoint pos;          /* Position as an SPoint */
+	double pm[2];        /* Proper motion long/lat in rad/year,
+	  PM in longitude has cos(lat) applied */
+	double parallax;    /* in rad */
+	double rv;          /* radial velocity in km/s */
+	int parallax_valid; /* 1 if the parallax really is a NULL */
+} phasevec;
+
+
+
diff --git a/expected/epochprop.out b/expected/epochprop.out
new file mode 100644
index 0000000..8541601
--- /dev/null
+++ b/expected/epochprop.out
@@ -0,0 +1,107 @@
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               546.9759,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+     to_char     |     to_char     | to_char  | to_char  |  to_char   | to_char  
+-----------------+-----------------+----------+----------+------------+----------
+  269.4742714391 |    4.4072939987 |  543.624 | -791.442 |  10235.412 | -110.450
+(1 row)
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               0,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+     to_char     |     to_char     | to_char | to_char  |  to_char   | to_char 
+-----------------+-----------------+---------+----------+------------+---------
+  269.4744079540 |    4.4055337210 |         | -801.210 |  10361.762 | 
+(1 row)
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               NULL,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+     to_char     |     to_char     | to_char | to_char  |  to_char   | to_char 
+-----------------+-----------------+---------+----------+------------+---------
+  269.4744079540 |    4.4055337210 |         | -801.210 |  10361.762 | 
+(1 row)
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), NULL,
+               20) AS tp) AS q;
+     to_char     |     to_char     | to_char  | to_char  |  to_char   | to_char  
+-----------------+-----------------+----------+----------+------------+----------
+  269.4476085384 |    4.7509315989 |   23.000 | -801.617 |  10361.984 |    2.159
+(1 row)
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               NULL, RADIANS(10362/3.6e6), -110,
+               120) AS tp) AS q;
+     to_char     |     to_char     | to_char  | to_char  |  to_char   | to_char  
+-----------------+-----------------+----------+----------+------------+----------
+  269.4520769500 |    5.0388680565 |   23.007 |    -.000 |  10368.061 |  -97.120
+(1 row)
+
+SELECT epoch_prop(NULL,
+               23,
+               0.01 , RADIANS(10362/3.6e6), -110,
+               120);
+ERROR:  NULL position not supported in epoch propagation
+SELECT epoch_prop_pos(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               20) AS tp;
+                   tp                    
+-----------------------------------------
+ (4.70274792658313 , 0.0829194509345993)
+(1 row)
+
+SELECT epoch_prop_pos(spoint(radians(269.45207695), radians(4.693364966)), 
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6),
+               20) AS tp;
+                   tp                    
+-----------------------------------------
+ (4.70274793061952 , 0.0829193989380876)
+(1 row)
+
diff --git a/pgs_epochprop.sql.in b/pgs_epochprop.sql.in
new file mode 100644
index 0000000..754c51a
--- /dev/null
+++ b/pgs_epochprop.sql.in
@@ -0,0 +1,41 @@
+-- ***********************************
+--
+-- functions for propagating positions
+--
+-- ***********************************
+
+CREATE FUNCTION epoch_prop(
+	pos spoint,
+	parallax DOUBLE PRECISION,
+	pmlng DOUBLE PRECISION,
+	pmlat DOUBLE PRECISION,
+	rv DOUBLE PRECISION,
+	delta_t DOUBLE PRECISION)
+	RETURNS double precision[6]
+	AS 'MODULE_PATHNAME', 'epoch_prop'
+	LANGUAGE 'c'
+	IMMUTABLE PARALLEL SAFE;
+
+CREATE FUNCTION epoch_prop_pos(
+	pos spoint,
+	parallax DOUBLE PRECISION,
+	pmlng DOUBLE PRECISION,
+	pmlat DOUBLE PRECISION,
+	rv DOUBLE PRECISION,
+	delta_t DOUBLE PRECISION)
+	RETURNS spoint
+	AS $body$
+		SELECT spoint(pv[1], pv[2])
+		FROM epoch_prop(pos, parallax, pmlng, pmlat, rv, delta_t) as pv
+	$body$ LANGUAGE SQL STABLE PARALLEL SAFE;
+
+CREATE FUNCTION epoch_prop_pos(
+	pos spoint,
+	pmlng DOUBLE PRECISION,
+	pmlat DOUBLE PRECISION,
+	delta_t DOUBLE PRECISION)
+	RETURNS spoint
+	AS $body$
+		SELECT spoint(pv[1], pv[2])
+		FROM epoch_prop(pos, 0, pmlng, pmlat, 0, delta_t) as pv
+	$body$ LANGUAGE SQL STABLE PARALLEL SAFE;
diff --git a/sql/epochprop.sql b/sql/epochprop.sql
new file mode 100644
index 0000000..fea9737
--- /dev/null
+++ b/sql/epochprop.sql
@@ -0,0 +1,78 @@
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               546.9759,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               0,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               NULL,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), NULL,
+               20) AS tp) AS q;
+
+SELECT 
+       to_char(DEGREES(tp[1]), '999D9999999999'),
+       to_char(DEGREES(tp[2]), '999D9999999999'),
+       to_char(tp[3], '999D999'),
+       to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+       to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+       to_char(tp[6], '999D999')
+FROM (
+       SELECT epoch_prop(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               NULL, RADIANS(10362/3.6e6), -110,
+               120) AS tp) AS q;
+
+SELECT epoch_prop(NULL,
+               23,
+               0.01 , RADIANS(10362/3.6e6), -110,
+               120);
+
+SELECT epoch_prop_pos(spoint(radians(269.45207695), radians(4.693364966)), 
+               23,
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               20) AS tp;
+
+SELECT epoch_prop_pos(spoint(radians(269.45207695), radians(4.693364966)), 
+               RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6),
+               20) AS tp;
diff --git a/vector3d.c b/vector3d.c
index adaf5f7..27b7d8f 100644
--- a/vector3d.c
+++ b/vector3d.c
@@ -18,6 +18,14 @@ vector3d_cross(Vector3D *out, const Vector3D *v1, const Vector3D *v2)
 	out->z = v1->x * v2->y - v1->y * v2->x;
 }
 
+void
+vector3d_addwithscalar(Vector3D *result, double scalar, const Vector3D *delta) 
+{
+	result->x = result->x + delta->x * scalar;
+	result->y = result->y + delta->y * scalar;
+	result->z = result->z + delta->z * scalar;
+}
+
 float8
 vector3d_scalar(Vector3D *v1, Vector3D *v2)
 {
diff --git a/vector3d.h b/vector3d.h
index 55d0f85..23ba9b9 100644
--- a/vector3d.h
+++ b/vector3d.h
@@ -37,4 +37,10 @@ float8	vector3d_scalar(Vector3D *v1, Vector3D *v2);
  */
 float8	vector3d_length(const Vector3D *v);
 
+/* 
+ * Calculate result + scalar*delta 
+ */
+void vector3d_addwithscalar(
+	Vector3D *result, double scalar, const Vector3D *delta);
+
 #endif

From 44edb5c7e945f90a2dbd76c60e2090a83e83dcf8 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Mon, 5 Sep 2022 14:07:46 +0200
Subject: [PATCH 10/16] Adding documentation for epoch propagation.

Also, fixing upgrade script generation.
---
 Makefile          |   6 +--
 doc/functions.sgm | 130 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 131 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 5faa5f4..a5bec05 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ DATA_built  = $(RELEASE_SQL) \
 			  pg_sphere--1.1.5beta0gavo--1.1.5beta2gavo.sql \
 			  pg_sphere--1.1.5beta2gavo--1.1.5beta4gavo.sql \
 			  pg_sphere--1.1.5beta4gavo--1.2.0.sql \
+			  pg_sphere--1.1.5beta4gavo--1.2.0.sql\
 			  pg_sphere--1.2.0--1.2.1.sql
 
 DOCS        = README.pg_sphere COPYRIGHT.pg_sphere
@@ -198,12 +199,9 @@ pg_sphere--1.1.5beta4gavo--1.2.0.sql: pgs_moc_ops.sql.in
 	cat $^ > $@
 ifeq ($(has_parallel), n)
 	sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
-
-pg_sphere--1.2.0-1.2.1.sql: pgs_epochprop.sql.in
-	cat upgrade_scripts/$@.in $^ > $@
 endif
 
-pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
+pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in pgs_epochprop.sql.in
 	cat $^ > $@
 ifeq ($(has_parallel), n)
 	sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
diff --git a/doc/functions.sgm b/doc/functions.sgm
index 1318141..30118bf 100644
--- a/doc/functions.sgm
+++ b/doc/functions.sgm
@@ -667,5 +667,133 @@
           </example>
 
         </sect2>
-        
+
+        <sect2 id="funcs.epochprop">
+          <title>
+            Epoch propagation
+          </title>
+
+          <sect3 id="funcs.epochprop.full">
+            <title>6-Parameter Epoch Propagation</title>
+            <funcsynopsis>
+              <funcprototype>
+                <funcdef><type>double precision[6]</type>
+                    <function>epoch_prop</function></funcdef>
+                  <paramdef>spoint <parameter>pos</parameter></paramdef>
+                  <paramdef>double precision <parameter>parallax</parameter></paramdef>
+                  <paramdef>double precision <parameter>pm_long</parameter></paramdef>
+                  <paramdef>double precision <parameter>pm_lat</parameter></paramdef>
+                  <paramdef>double precision <parameter>radial_velocity</parameter></paramdef>
+                  <paramdef>double precision <parameter>delta_t</parameter></paramdef>
+              </funcprototype>
+            </funcsynopsis>
+            <para>
+              Propagates a spherical phase vector in time (in particular,
+              applies proper motion to positions)
+            </para>
+            <para>
+              Following both pg_sphere and, where missing, astronomical 
+              conventions makes units somewhat eclectic here; pm_long and pm_lat
+              need to be in rad/yr, whereas parallax is in mas, and 
+              radial_velocity in km/s.  The time difference must be in 
+              (Julian) years.
+            </para>
+
+            <para>
+              This function returns a 6-array of [long, lat, parallax, 
+              pm_long, pm_lat, radial_velocity] of the corresponding values
+              delta_t years after the reference epoch for the original position.
+              As in the function arguments, long and lat are in rad, pm_lon and
+              pm_lat are in rad/yr, parallax is in mas, and radial_velocity is
+              in km/s.  If you are only interested in the position, consider
+              the epoch_prop_pos functions below that have a somewhat less
+              contorted signature.
+            </para>
+
+            <para>
+              It is an error to have either pos or delta_t NULL.  For all
+              other arguments, NULLs are turned into 0s, except for parallax,
+              where some very small default is put in.  In that case,
+              both parallax and radial_velocity will be NULL in the output
+              array.
+            </para>
+
+            <para>
+              This uses the rigorous method derived in "The Hipparcos and Tycho
+              Catalogues", ESA Special Publication 1200 (1997), p 94f.  It does
+              not take into account relativistic effects, and it also does not
+              account for secular aberration.
+            </para>
+            <example>
+              <title>Propagating Barnard's star into the past</title>
+              <programlisting><![CDATA[
+SELECT 
+   to_char(DEGREES(tp[1]), '999D9999999999'),
+   to_char(DEGREES(tp[2]), '999D9999999999'),
+   to_char(tp[3], '999D999'),
+   to_char(DEGREES(tp[4])*3.6e6, '999D999'),
+   to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
+   to_char(tp[6], '999D999')
+FROM (
+  SELECT epoch_prop(
+    spoint(radians(269.45207695), radians(4.693364966)), 546.9759,
+    RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
+               -100) AS tp) AS q;
+     to_char     |     to_char     | to_char  | to_char  |  to_char   | to_char  
+-----------------+-----------------+----------+----------+------------+----------
+  269.4742714391 |    4.4072939987 |  543.624 | -791.442 |  10235.412 | -110.450
+       ]]></programlisting>
+            </example>
+
+        </sect3>
+        <sect3>
+          <title>Epoch Propagation of Positions Only</title>
+          <funcsynopsis>
+            <funcprototype>
+              <funcdef><type>spoint</type>
+                  <function>epoch_prop_pos</function></funcdef>
+                <paramdef>spoint <parameter>pos</parameter></paramdef>
+                <paramdef>double precision <parameter>parallax</parameter></paramdef>
+                <paramdef>double precision <parameter>pm_long</parameter></paramdef>
+                <paramdef>double precision <parameter>pm_lat</parameter></paramdef>
+                <paramdef>double precision <parameter>radial_velocity</parameter></paramdef>
+                <paramdef>double precision <parameter>delta_t</parameter></paramdef>
+            </funcprototype>
+          </funcsynopsis>
+          <funcsynopsis>
+            <funcprototype>
+              <funcdef><type>spoint</type>
+                  <function>epoch_prop_pos</function></funcdef>
+                <paramdef>spoint <parameter>pos</parameter></paramdef>
+                <paramdef>double precision <parameter>pm_long</parameter></paramdef>
+                <paramdef>double precision <parameter>pm_lat</parameter></paramdef>
+                <paramdef>double precision <parameter>delta_t</parameter></paramdef>
+            </funcprototype>
+          </funcsynopsis>
+          <para>
+            These are simplified versions of epoch_prop returning only spoints;
+            the propagated values for the other coordinates are discarded
+            (but still internallay computed; these functions do not
+            run any faster than epoch_prop itself).
+          </para>
+
+          <para>
+            As with epoch_prop itself, missing values (except for pos and 
+            delta_t) are substituted by 0 (or a very small value in the
+            case of parallax).
+          </para>
+          <example>
+            <title>Barnard's star, position and proper motion</title>
+            <programlisting><![CDATA[
+SELECT epoch_prop_pos(
+  spoint(radians(269.45207695), radians(4.693364966)), 
+  RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6),
+  20) AS tp;
+                   tp                    
+-----------------------------------------
+ (4.70274793061952 , 0.0829193989380876)
+       ]]></programlisting>
+          </example>
+        </sect3>
+      </sect2>
     </sect1>

From 7c3e5a46f91c71ea6f93fbdd9963a84e6684c4c0 Mon Sep 17 00:00:00 2001
From: Markus Demleitner <m@tfiu.de>
Date: Fri, 16 Jun 2023 10:25:37 +0200
Subject: [PATCH 11/16] Now catching NULLs in the delta-t argument of
 epoch_prop, too.

(and fixing a duplicated line in the Makefile too trivial to warrant another
rebase)
---
 Makefile    |  1 -
 epochprop.c | 18 +++++++++++-------
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Makefile b/Makefile
index a5bec05..ade3a5f 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,6 @@ DATA_built  = $(RELEASE_SQL) \
 			  pg_sphere--1.1.5beta0gavo--1.1.5beta2gavo.sql \
 			  pg_sphere--1.1.5beta2gavo--1.1.5beta4gavo.sql \
 			  pg_sphere--1.1.5beta4gavo--1.2.0.sql \
-			  pg_sphere--1.1.5beta4gavo--1.2.0.sql\
 			  pg_sphere--1.2.0--1.2.1.sql
 
 DOCS        = README.pg_sphere COPYRIGHT.pg_sphere
diff --git a/epochprop.c b/epochprop.c
index 116dc0b..4c7af21 100644
--- a/epochprop.c
+++ b/epochprop.c
@@ -25,7 +25,7 @@ PG_FUNCTION_INFO_V1(epoch_prop);
 /* A_nu as per ESA/SP-1200 */
 #define A_NU (AU/(J_YEAR))
 
-/* Following SOFA, we use 1e-7 arcsec as minimal parallax 
+/* Following SOFA, we use 1e-7 arcsec as minimal parallax
 	("celestial sphere"); parallax=0 exactly means "infinite distance", which
 	leads to all sorts for problems; our parallaxes come in in mas, so: */
 #define PX_MIN 1e-7*1000
@@ -59,11 +59,11 @@ static void propagate_phasevec(
 	spoint_vector3d(&r0, &(pv->pos));
 
 	p0.x = -sin(pv->pos.lng);
-	p0.y = cos(pv->pos.lng); 
+	p0.y = cos(pv->pos.lng);
 	p0.z = 0;
 
 	q0.x = -sin(pv->pos.lat) * cos(pv->pos.lng);
-	q0.y = -sin(pv->pos.lat) * sin(pv->pos.lng); 
+	q0.y = -sin(pv->pos.lat) * sin(pv->pos.lng);
 	q0.z = cos(pv->pos.lat);
 
 	/* the original proper motion vector */
@@ -117,17 +117,17 @@ static void propagate_phasevec(
 }
 
 
-/* 
+/*
 	Propagate a position with proper motions and optionally parallax
 	and radial velocity.
 
 	Arguments: pos0 (spoint), pm_long, pm_lat (in rad/yr)
-	par (parallax, mas), rv (in km/s), delta_t (in years) 
+	par (parallax, mas), rv (in km/s), delta_t (in years)
 
 	This returns a 6-array of lat, long (in rad), parallax (in mas)
 	pmlat, pmlong (in rad/yr), rv (in km/s).
 */
-Datum 
+Datum
 epoch_prop(PG_FUNCTION_ARGS) {
 	double delta_t;
 	phasevec input, output;
@@ -135,7 +135,7 @@ epoch_prop(PG_FUNCTION_ARGS) {
 	Datum retvals[6];
 
 	if (PG_ARGISNULL(0)) {
-		ereport(ERROR, 
+		ereport(ERROR,
 			(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 				errmsg("NULL position not supported in epoch propagation"))); }
 	memcpy(&(input.pos), (void*)PG_GETARG_POINTER(0), sizeof(SPoint));
@@ -164,6 +164,10 @@ epoch_prop(PG_FUNCTION_ARGS) {
 		input.rv = PG_GETARG_FLOAT8(4);
 	}
 
+	if (PG_ARGISNULL(5)) {
+		ereport(ERROR,
+			(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				errmsg("NULL delta t not supported in epoch propagation"))); }
 	delta_t = PG_GETARG_FLOAT8(5);
 
 	propagate_phasevec(&input, delta_t, &output);

From 094bec2450c6dae7d29678831c93aa7cb121b0e3 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 20 Jun 2023 14:11:57 +0300
Subject: [PATCH 12/16] Source files are moved into src subdirectory.

---
 Makefile                                   | 18 +++++++++---------
 box.c => src/box.c                         |  0
 box.h => src/box.h                         |  0
 circle.c => src/circle.c                   |  0
 circle.h => src/circle.h                   |  0
 ellipse.c => src/ellipse.c                 |  0
 ellipse.h => src/ellipse.h                 |  0
 epochprop.c => src/epochprop.c             |  0
 epochprop.h => src/epochprop.h             |  0
 euler.c => src/euler.c                     |  0
 euler.h => src/euler.h                     |  0
 gist.c => src/gist.c                       |  0
 gist.h => src/gist.h                       |  0
 gnomo.c => src/gnomo.c                     |  0
 gnomo.h => src/gnomo.h                     |  0
 gq_cache.c => src/gq_cache.c               |  0
 healpix.c => src/healpix.c                 |  0
 key.c => src/key.c                         |  0
 key.h => src/key.h                         |  0
 line.c => src/line.c                       |  0
 line.h => src/line.h                       |  0
 moc.c => src/moc.c                         |  0
 output.c => src/output.c                   |  0
 path.c => src/path.c                       |  0
 path.h => src/path.h                       |  0
 pg_sphere.h => src/pg_sphere.h             |  0
 pgs_chealpix.h => src/pgs_chealpix.h       |  0
 pgs_healpix.h => src/pgs_healpix.h         |  0
 pgs_moc.h => src/pgs_moc.h                 |  0
 pgs_process_moc.h => src/pgs_process_moc.h |  0
 pgs_util.h => src/pgs_util.h               |  0
 point.c => src/point.c                     |  0
 point.h => src/point.h                     |  0
 polygon.c => src/polygon.c                 |  0
 polygon.h => src/polygon.h                 |  0
 process_moc.cpp => src/process_moc.cpp     |  0
 sbuffer.c => src/sbuffer.c                 |  0
 sbuffer.h => src/sbuffer.h                 |  0
 sparse.c => src/sparse.c                   |  0
 sparse.h => src/sparse.h                   |  0
 sparse.y => src/sparse.y                   |  0
 sscan.c => src/sscan.c                     |  0
 sscan.l => src/sscan.l                     |  0
 types.h => src/types.h                     |  0
 vector3d.c => src/vector3d.c               |  0
 vector3d.h => src/vector3d.h               |  0
 46 files changed, 9 insertions(+), 9 deletions(-)
 rename box.c => src/box.c (100%)
 rename box.h => src/box.h (100%)
 rename circle.c => src/circle.c (100%)
 rename circle.h => src/circle.h (100%)
 rename ellipse.c => src/ellipse.c (100%)
 rename ellipse.h => src/ellipse.h (100%)
 rename epochprop.c => src/epochprop.c (100%)
 rename epochprop.h => src/epochprop.h (100%)
 rename euler.c => src/euler.c (100%)
 rename euler.h => src/euler.h (100%)
 rename gist.c => src/gist.c (100%)
 rename gist.h => src/gist.h (100%)
 rename gnomo.c => src/gnomo.c (100%)
 rename gnomo.h => src/gnomo.h (100%)
 rename gq_cache.c => src/gq_cache.c (100%)
 rename healpix.c => src/healpix.c (100%)
 rename key.c => src/key.c (100%)
 rename key.h => src/key.h (100%)
 rename line.c => src/line.c (100%)
 rename line.h => src/line.h (100%)
 rename moc.c => src/moc.c (100%)
 rename output.c => src/output.c (100%)
 rename path.c => src/path.c (100%)
 rename path.h => src/path.h (100%)
 rename pg_sphere.h => src/pg_sphere.h (100%)
 rename pgs_chealpix.h => src/pgs_chealpix.h (100%)
 rename pgs_healpix.h => src/pgs_healpix.h (100%)
 rename pgs_moc.h => src/pgs_moc.h (100%)
 rename pgs_process_moc.h => src/pgs_process_moc.h (100%)
 rename pgs_util.h => src/pgs_util.h (100%)
 rename point.c => src/point.c (100%)
 rename point.h => src/point.h (100%)
 rename polygon.c => src/polygon.c (100%)
 rename polygon.h => src/polygon.h (100%)
 rename process_moc.cpp => src/process_moc.cpp (100%)
 rename sbuffer.c => src/sbuffer.c (100%)
 rename sbuffer.h => src/sbuffer.h (100%)
 rename sparse.c => src/sparse.c (100%)
 rename sparse.h => src/sparse.h (100%)
 rename sparse.y => src/sparse.y (100%)
 rename sscan.c => src/sscan.c (100%)
 rename sscan.l => src/sscan.l (100%)
 rename types.h => src/types.h (100%)
 rename vector3d.c => src/vector3d.c (100%)
 rename vector3d.h => src/vector3d.h (100%)

diff --git a/Makefile b/Makefile
index ade3a5f..7926e5a 100644
--- a/Makefile
+++ b/Makefile
@@ -4,11 +4,11 @@ PGSPHERE_VERSION = 1.2.1
 SRC_DIR = $(shell basename $(shell pwd))
 
 MODULE_big = pg_sphere
-OBJS       = sscan.o sparse.o sbuffer.o vector3d.o point.o \
-             euler.o circle.o line.o ellipse.o polygon.o \
-             path.o box.o output.o gq_cache.o gist.o key.o \
-             gnomo.o healpix.o moc.o process_moc.o healpix_bare/healpix_bare.o \
-             epochprop.o
+OBJS       = src/sscan.o src/sparse.o src/sbuffer.o src/vector3d.o src/point.o \
+             src/euler.o src/circle.o src/line.o src/ellipse.o src/polygon.o \
+             src/path.o src/box.o src/output.o src/gq_cache.o src/gist.o src/key.o \
+             src/gnomo.o src/healpix.o src/moc.o src/process_moc.o healpix_bare/healpix_bare.o \
+             src/epochprop.o
 
 EXTENSION   = pg_sphere
 RELEASE_SQL = $(EXTENSION)--$(PGSPHERE_VERSION).sql
@@ -208,16 +208,16 @@ endif
 
 # end of local stuff
 
-sscan.o : sparse.c
+src/sscan.o : src/sparse.c
 
-sparse.c: sparse.y
+src/sparse.c: src/sparse.y
 ifdef YACC
-	$(YACC) -d $(YFLAGS) -p sphere_yy -o sparse.c $<
+	$(YACC) -d $(YFLAGS) -p sphere_yy -o $@ $<
 else
 	@$(missing) bison $< $@
 endif
 
-sscan.c : sscan.l
+src/sscan.c : src/sscan.l
 ifdef FLEX
 	$(FLEX) $(FLEXFLAGS) -Psphere -o$@ $<
 else
diff --git a/box.c b/src/box.c
similarity index 100%
rename from box.c
rename to src/box.c
diff --git a/box.h b/src/box.h
similarity index 100%
rename from box.h
rename to src/box.h
diff --git a/circle.c b/src/circle.c
similarity index 100%
rename from circle.c
rename to src/circle.c
diff --git a/circle.h b/src/circle.h
similarity index 100%
rename from circle.h
rename to src/circle.h
diff --git a/ellipse.c b/src/ellipse.c
similarity index 100%
rename from ellipse.c
rename to src/ellipse.c
diff --git a/ellipse.h b/src/ellipse.h
similarity index 100%
rename from ellipse.h
rename to src/ellipse.h
diff --git a/epochprop.c b/src/epochprop.c
similarity index 100%
rename from epochprop.c
rename to src/epochprop.c
diff --git a/epochprop.h b/src/epochprop.h
similarity index 100%
rename from epochprop.h
rename to src/epochprop.h
diff --git a/euler.c b/src/euler.c
similarity index 100%
rename from euler.c
rename to src/euler.c
diff --git a/euler.h b/src/euler.h
similarity index 100%
rename from euler.h
rename to src/euler.h
diff --git a/gist.c b/src/gist.c
similarity index 100%
rename from gist.c
rename to src/gist.c
diff --git a/gist.h b/src/gist.h
similarity index 100%
rename from gist.h
rename to src/gist.h
diff --git a/gnomo.c b/src/gnomo.c
similarity index 100%
rename from gnomo.c
rename to src/gnomo.c
diff --git a/gnomo.h b/src/gnomo.h
similarity index 100%
rename from gnomo.h
rename to src/gnomo.h
diff --git a/gq_cache.c b/src/gq_cache.c
similarity index 100%
rename from gq_cache.c
rename to src/gq_cache.c
diff --git a/healpix.c b/src/healpix.c
similarity index 100%
rename from healpix.c
rename to src/healpix.c
diff --git a/key.c b/src/key.c
similarity index 100%
rename from key.c
rename to src/key.c
diff --git a/key.h b/src/key.h
similarity index 100%
rename from key.h
rename to src/key.h
diff --git a/line.c b/src/line.c
similarity index 100%
rename from line.c
rename to src/line.c
diff --git a/line.h b/src/line.h
similarity index 100%
rename from line.h
rename to src/line.h
diff --git a/moc.c b/src/moc.c
similarity index 100%
rename from moc.c
rename to src/moc.c
diff --git a/output.c b/src/output.c
similarity index 100%
rename from output.c
rename to src/output.c
diff --git a/path.c b/src/path.c
similarity index 100%
rename from path.c
rename to src/path.c
diff --git a/path.h b/src/path.h
similarity index 100%
rename from path.h
rename to src/path.h
diff --git a/pg_sphere.h b/src/pg_sphere.h
similarity index 100%
rename from pg_sphere.h
rename to src/pg_sphere.h
diff --git a/pgs_chealpix.h b/src/pgs_chealpix.h
similarity index 100%
rename from pgs_chealpix.h
rename to src/pgs_chealpix.h
diff --git a/pgs_healpix.h b/src/pgs_healpix.h
similarity index 100%
rename from pgs_healpix.h
rename to src/pgs_healpix.h
diff --git a/pgs_moc.h b/src/pgs_moc.h
similarity index 100%
rename from pgs_moc.h
rename to src/pgs_moc.h
diff --git a/pgs_process_moc.h b/src/pgs_process_moc.h
similarity index 100%
rename from pgs_process_moc.h
rename to src/pgs_process_moc.h
diff --git a/pgs_util.h b/src/pgs_util.h
similarity index 100%
rename from pgs_util.h
rename to src/pgs_util.h
diff --git a/point.c b/src/point.c
similarity index 100%
rename from point.c
rename to src/point.c
diff --git a/point.h b/src/point.h
similarity index 100%
rename from point.h
rename to src/point.h
diff --git a/polygon.c b/src/polygon.c
similarity index 100%
rename from polygon.c
rename to src/polygon.c
diff --git a/polygon.h b/src/polygon.h
similarity index 100%
rename from polygon.h
rename to src/polygon.h
diff --git a/process_moc.cpp b/src/process_moc.cpp
similarity index 100%
rename from process_moc.cpp
rename to src/process_moc.cpp
diff --git a/sbuffer.c b/src/sbuffer.c
similarity index 100%
rename from sbuffer.c
rename to src/sbuffer.c
diff --git a/sbuffer.h b/src/sbuffer.h
similarity index 100%
rename from sbuffer.h
rename to src/sbuffer.h
diff --git a/sparse.c b/src/sparse.c
similarity index 100%
rename from sparse.c
rename to src/sparse.c
diff --git a/sparse.h b/src/sparse.h
similarity index 100%
rename from sparse.h
rename to src/sparse.h
diff --git a/sparse.y b/src/sparse.y
similarity index 100%
rename from sparse.y
rename to src/sparse.y
diff --git a/sscan.c b/src/sscan.c
similarity index 100%
rename from sscan.c
rename to src/sscan.c
diff --git a/sscan.l b/src/sscan.l
similarity index 100%
rename from sscan.l
rename to src/sscan.l
diff --git a/types.h b/src/types.h
similarity index 100%
rename from types.h
rename to src/types.h
diff --git a/vector3d.c b/src/vector3d.c
similarity index 100%
rename from vector3d.c
rename to src/vector3d.c
diff --git a/vector3d.h b/src/vector3d.h
similarity index 100%
rename from vector3d.h
rename to src/vector3d.h

From 0966a37757f4456945e8e05a4cb7c64cbee6fba6 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 20 Jun 2023 16:51:46 +0300
Subject: [PATCH 13/16] Updated test script to support actual pg versions for
 testing.

---
 .travis.yml | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 60ba10d..e2b829c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,13 +2,12 @@
 ---
 # versions to run on
 env:
-  - PG_SUPPORTED_VERSIONS=9.4 # introduces psprintf
-  - PG_SUPPORTED_VERSIONS=9.5
-  - PG_SUPPORTED_VERSIONS=9.6
   - PG_SUPPORTED_VERSIONS=10
   - PG_SUPPORTED_VERSIONS=11
   - PG_SUPPORTED_VERSIONS=12
   - PG_SUPPORTED_VERSIONS=13
+  - PG_SUPPORTED_VERSIONS=14
+  - PG_SUPPORTED_VERSIONS=15
 
 language: C
 dist: bionic

From fb6e62d3fac7039e80f29d5e93b62c179f960bb9 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 20 Jun 2023 16:00:33 +0300
Subject: [PATCH 14/16] Angled braces in #include statement were replaced by
 quotes.

---
 src/epochprop.c | 2 +-
 src/gnomo.c     | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/epochprop.c b/src/epochprop.c
index 4c7af21..ab9abc8 100644
--- a/src/epochprop.c
+++ b/src/epochprop.c
@@ -8,7 +8,7 @@ Transformation: Rigorous Treatment'; cf.
 */
 
 #include <math.h>
-#include <pgs_util.h>
+#include "pgs_util.h"
 
 #include "point.h"
 #include "epochprop.h"
diff --git a/src/gnomo.c b/src/gnomo.c
index c4c554b..a13c8dc 100644
--- a/src/gnomo.c
+++ b/src/gnomo.c
@@ -1,8 +1,8 @@
 #include <postgres.h>
 #include <fmgr.h>
 
-#include <gnomo.h>
-#include <point.h> /* SPoint from pgsphere */
+#include "gnomo.h"
+#include "point.h" /* SPoint from pgsphere */
 
 #include <math.h>
 

From 9b1153c84d64b051d16f15a2771afc58eeeacf66 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Wed, 21 Jun 2023 22:39:50 +0300
Subject: [PATCH 15/16] Wrapped long line.

---
 Makefile | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 7926e5a..bc82eb4 100644
--- a/Makefile
+++ b/Makefile
@@ -6,9 +6,9 @@ SRC_DIR = $(shell basename $(shell pwd))
 MODULE_big = pg_sphere
 OBJS       = src/sscan.o src/sparse.o src/sbuffer.o src/vector3d.o src/point.o \
              src/euler.o src/circle.o src/line.o src/ellipse.o src/polygon.o \
-             src/path.o src/box.o src/output.o src/gq_cache.o src/gist.o src/key.o \
-             src/gnomo.o src/healpix.o src/moc.o src/process_moc.o healpix_bare/healpix_bare.o \
-             src/epochprop.o
+             src/path.o src/box.o src/output.o src/gq_cache.o src/gist.o \
+             src/key.o src/gnomo.o src/healpix.o src/moc.o src/process_moc.o \
+             healpix_bare/healpix_bare.o src/epochprop.o
 
 EXTENSION   = pg_sphere
 RELEASE_SQL = $(EXTENSION)--$(PGSPHERE_VERSION).sql

From 894f9bb1c44926716449ddd1aefb795e014fa323 Mon Sep 17 00:00:00 2001
From: Vitaly Davydov <v.davydov@postgrespro.ru>
Date: Tue, 27 Jun 2023 12:07:28 +0300
Subject: [PATCH 16/16] [ISSUE-10] Implemented propagation of pg sphere
 version.

---
 Makefile          |  3 +++
 expected/init.out |  6 ++++++
 sql/init.sql      |  2 ++
 src/output.c      | 14 +++++++++++---
 4 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index bc82eb4..a98cccd 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,9 @@ TESTS       = init_test tables points euler circle line ellipse poly path box in
 			  contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
 			  moc mocautocast epochprop
 
+PG_CFLAGS	+= -DPGSPHERE_VERSION=$(PGSPHERE_VERSION)
+PG_CPPFLAGS	+= -DPGSPHERE_VERSION=$(PGSPHERE_VERSION)
+
 ifndef CXXFLAGS
 # no support for CXXFLAGS in PGXS before v11
 CXXFLAGS = -Wall -Wpointer-arith -Wendif-labels \
diff --git a/expected/init.out b/expected/init.out
index 0af7764..375eeaf 100644
--- a/expected/init.out
+++ b/expected/init.out
@@ -3,3 +3,9 @@
 -- does not depend on contents of pg_sphere.sql.
 --
 CREATE EXTENSION pg_sphere;
+select pg_sphere_version();
+ pg_sphere_version 
+-------------------
+ 1.2.1
+(1 row)
+
diff --git a/sql/init.sql b/sql/init.sql
index 0af7764..05b3516 100644
--- a/sql/init.sql
+++ b/sql/init.sql
@@ -3,3 +3,5 @@
 -- does not depend on contents of pg_sphere.sql.
 --
 CREATE EXTENSION pg_sphere;
+
+select pg_sphere_version();
diff --git a/src/output.c b/src/output.c
index 865e458..60e7464 100644
--- a/src/output.c
+++ b/src/output.c
@@ -1,5 +1,12 @@
 #include "types.h"
 
+#if !defined(PGSPHERE_VERSION)
+#error "PGSPHERE_VERSION macro is not set"
+#endif
+
+#define PGSPHERE_STRINGIFY_INTERNAL(x) #x
+#define PGSPHERE_STRINGIFY(x) PGSPHERE_STRINGIFY_INTERNAL(x)
+
 /* Output functions */
 
 
@@ -527,7 +534,8 @@ spherebox_out(PG_FUNCTION_ARGS)
 Datum
 pg_sphere_version(PG_FUNCTION_ARGS)
 {
-	char	   *buffer = (char *) palloc(20);
-	sprintf(buffer, "1.1.5");
-	PG_RETURN_CSTRING(buffer);
+	const char	*s = PGSPHERE_STRINGIFY(PGSPHERE_VERSION);
+	char		*p = (char *)palloc(strlen(s) + 1);
+	strcpy(p, s);
+	PG_RETURN_CSTRING(p);
 }
