lp1863252 toward geosort
authorMike Rylander <mrylander@gmail.com>
Thu, 10 Dec 2020 21:41:24 +0000 (16:41 -0500)
committerBill Erickson <berickxx@gmail.com>
Thu, 11 Mar 2021 21:00:52 +0000 (16:00 -0500)
* Add earthdistance-based org ranking
* Accept user input and get best-guess coordinates from that

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Terran McCanna <tmccanna@georgialibraries.org>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
Open-ILS/src/sql/Pg/990.schema.unapi.sql
Open-ILS/src/sql/Pg/create_database_extensions.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.geosort.sql
Open-ILS/src/templates-bootstrap/opac/parts/record/copy_table.tt2
Open-ILS/src/templates/opac/parts/record/copy_table.tt2

index 9deb0bf..ca5b34e 100644 (file)
@@ -341,10 +341,26 @@ sub mk_copy_query {
         }};
     };
 
+       my $ou_sort_param = [$org, $pref_ou ];
+       my $gl = $self->cgi->param('geographic-location');
+       if ($gl) {
+               my $geo = OpenSRF::AppSession->create("open-ils.geo");
+               my $coords = $geo
+                       ->request('open-ils.geo.retrieve_coordinates', $org, scalar $gl)
+                       ->gather(1);
+               if ($coords
+                       && ref($coords)
+                       && $$coords{latitude}
+                       && $$coords{longitude}
+               ) {
+                       push(@$ou_sort_param, $$coords{latitude}, $$coords{longitude});
+               }
+       }
+
     # Unsure if we want these in the shared function, leaving here for now
     unshift(@{$query->{order_by}},
         { class => "aou", field => 'id',
-          transform => 'evergreen.rank_ou', params => [$org, $pref_ou]
+          transform => 'evergreen.rank_ou', params => $ou_sort_param
         }
     );
     push(@{$query->{order_by}},
index b761fbb..9c334c8 100644 (file)
@@ -40,6 +40,42 @@ RETURNS INTEGER AS $$
     );
 $$ LANGUAGE SQL STABLE;
 
+-- geolocation-aware variant
+CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT, plat FLOAT, plon FLOAT)
+RETURNS INTEGER AS $$
+    SELECT COALESCE(
+
+        -- lib matches search_lib
+        (SELECT CASE WHEN $1 = $2 THEN -20000 END),
+
+        -- lib matches pref_lib
+        (SELECT CASE WHEN $1 = $3 THEN -10000 END),
+
+
+        -- pref_lib is a child of search_lib and lib is a child of pref lib.  
+        -- For example, searching CONS, pref lib is SYS1, 
+        -- copies at BR1 and BR2 sort to the front.
+        (SELECT distance - 5000
+            FROM actor.org_unit_descendants_distance($3) 
+            WHERE id = $1 AND $3 IN (
+                SELECT id FROM actor.org_unit_descendants($2))),
+
+        -- lib is a child of search_lib
+        (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),
+
+        -- all others pay cash
+        1000
+    ) + ((SELECT CASE WHEN addr.latitude IS NULL THEN 0 ELSE -20038 END) + (earth_distance( -- shortest GC distance is returned, only half the circumfrence is needed
+            ll_to_earth(
+                COALESCE(addr.latitude,plat), -- if the org has no coords, we just
+                COALESCE(addr.longitude,plon) -- force 0 distance and let the above tie-break
+            ),ll_to_earth(plat,plon)
+        ) / 1000)::INT ) -- earth_distance is in meters, convert to kilometers and subtract from largest distance
+    FROM actor.org_unit org
+                LEFT JOIN actor.org_address addr ON (org.billing_address = addr.id)
+       WHERE org.id = $1;
+$$ LANGUAGE SQL STABLE;
+
 -- this version exists mainly to accommodate JSON query transform limitations
 -- (the transform argument must be an IDL field, not an entire row/object)
 -- XXX is there another way?
index 013032c..b37f79e 100644 (file)
@@ -21,3 +21,4 @@ CREATE EXTENSION hstore;
 CREATE EXTENSION intarray;
 CREATE EXTENSION pgcrypto;
 CREATE EXTENSION unaccent;
+CREATE EXTENSION earthdistance CASCADE;
index 9966492..aeb39b7 100644 (file)
@@ -3,6 +3,8 @@ BEGIN;
 -- check whether patch can be applied
 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 
+CREATE EXTENSION earthdistance CASCADE;
+
 -- 005.schema.actors.sql
 
 -- CREATE TABLE actor.org_address (
@@ -90,4 +92,40 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
     'Administer geographic location services', 'ppl', 'description'))
 ;
 
+-- geolocation-aware variant
+CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT, plat FLOAT, plon FLOAT)
+RETURNS INTEGER AS $$
+    SELECT COALESCE(
+
+        -- lib matches search_lib
+        (SELECT CASE WHEN $1 = $2 THEN -20000 END),
+
+        -- lib matches pref_lib
+        (SELECT CASE WHEN $1 = $3 THEN -10000 END),
+
+
+        -- pref_lib is a child of search_lib and lib is a child of pref lib.
+        -- For example, searching CONS, pref lib is SYS1,
+        -- copies at BR1 and BR2 sort to the front.
+        (SELECT distance - 5000
+            FROM actor.org_unit_descendants_distance($3)
+            WHERE id = $1 AND $3 IN (
+                SELECT id FROM actor.org_unit_descendants($2))),
+
+        -- lib is a child of search_lib
+        (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),
+
+        -- all others pay cash
+        1000
+    ) + ((SELECT CASE WHEN addr.latitude IS NULL THEN 0 ELSE -20038 END) + (earth_distance( -- shortest GC distance is returned, only half the circumfrence is needed
+            ll_to_earth(
+                COALESCE(addr.latitude,plat), -- if the org has no coords, we just
+                COALESCE(addr.longitude,plon) -- force 0 distance and let the above tie-break
+            ),ll_to_earth(plat,plon)
+        ) / 1000)::INT ) -- earth_distance is in meters, convert to kilometers and subtract from largest distance
+    FROM actor.org_unit org
+         LEFT JOIN actor.org_address addr ON (org.billing_address = addr.id)
+    WHERE org.id = $1;
+$$ LANGUAGE SQL STABLE;
+
 COMMIT;
index 034760c..9bf4fd9 100755 (executable)
@@ -30,6 +30,18 @@ IF has_copies or ctx.foreign_copies;
   depth = CGI.param('copy_depth').defined ? CGI.param('copy_depth') : CGI.param('depth').defined ? CGI.param('depth') : ctx.copy_summary.last.depth;
   total_copies = ctx.copy_summary.$depth.count;
 %]
+[% IF ctx.geo_sort %]
+<form method="GET">
+[% FOREACH p IN CGI.params.keys; NEXT IF p == 'geographic-location' %]
+  <input type="hidden" name="[% p | html %]" value="[% CGI.params.$p | html %]"/>
+[% END %]
+<span with="50%">
+    [% l("Sort by distance from:") %]
+    <input type="text" id="geographic-location-box" name="geographic-location" aria-label="[% l('Enter address or postal code') %]" placeholder="[% l('Enter address/postal code') %]" class="search-box" x-webkit-speech="" value="[% p = 'geographic-location'; CGI.params.$p %]"></input>
+    <button type="submit" class="btn btn-confirm">[% l('Go') %]</button>
+</span>
+</form>
+[% END %]
 <table class="container-fluid table table-hover mt-4 miniTable copyTable w-100" >
     <thead>
         <tr>
index 15f2dba..f62e96e 100644 (file)
@@ -32,11 +32,17 @@ IF has_copies or ctx.foreign_copies;
 %]
 [% use_courses = (ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1) ? 1 : 0 %]
 [% IF ctx.geo_sort %]
+<form method="GET">
+[% FOREACH p IN CGI.params.keys; NEXT IF p == 'geographic-location' %]
+  <input type="hidden" name="[% p | html %]" value="[% CGI.params.$p | html %]"/>
+[% END %]
+<span with="50%">
 <th colspan="6">
     [% l("Sort by distance from:") %]
-    <input type="text" id="geographic-location-box" name="geographic-location" aria-label="[% l('Enter address or postal code') %]" placeholder="[% l('Enter address/postal code') %]" class="search-box" x-webkit-speech=""></input>
-    <input id="geographic-location-submit-go" type="submit" value="[% l('Go') %]" class="opac-button" onclick=""></input>
-</th>
+    <input type="text" id="geographic-location-box" name="geographic-location" aria-label="[% l('Enter address or postal code') %]" placeholder="[% l('Enter address/postal code') %]" class="search-box" x-webkit-speech="" value="[% p = 'geographic-location'; CGI.params.$p %]"></input>
+    <button type="submit" class="opac-button">[% l('Go') %]</button>
+</span>
+</form>
 [% END %]
 <table class="table_no_border_space table_no_cell_pad table_no_border" width="100%" id="rdetails_status">
     <thead>