%--------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%--------------------------------------------------%
% Copyright (C) 2006-2007 The University of Melbourne.
% Copyright (C) 2014-2019, 2025 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%--------------------------------------------------%
%
% File: rtree.m.
% Main author: gjd.
% Stability: high.
%
% This module provides a region tree (R-tree) ADT. A region tree associates
% values with regions in some space, e.g. rectangles in the 2D plane, or
% bounding spheres in 3D space. Region trees accept spatial queries, e.g. a
% typical usage is "find all pubs within a 2km radius".
%
% This module also provides the typeclass region(K) which allows the user to
% define new regions and spaces. Three "builtin" instances for region(K)
% are provided: region(interval), region(box) and region(box3d)
% corresponding to "square" regions in one, two and three dimensional spaces
% respectively.
%
%--------------------------------------------------%
%--------------------------------------------------%
:- module rtree.
:- interface.
:- import_module list.
%--------------------------------------------------%
:- type rtree(K, V).
:- typeclass region(K) where [
% Succeeds if-and-only-if two regions intersect.
%
pred intersects(K::in, K::in) is semidet,
% Succeeds if-and-only-if the first region
% is contained within the second.
%
pred contains(K::in, K::in) is semidet,
% Returns the "size" of a region.
% e.g. for a two dimensional box one possible measure of "size"
% would be the area.
%
func size(K) = float,
% Return a region that contains both input regions.
% The region returned should be minimal region that contains
% both input regions.
%
func bounding_region(K, K) = K,
% Computes the size of the bounding region returned by
% bounding_region/2, i.e.
%
% bounding_region_size(K1, K2) = size(bounding_region(K1, K2)).
%
% While the above definition would suffice, a more efficient
% implementation often exists, e.g. for intervals:
%
% bounding_region_size(interval(X0, X1), interval(Y0, Y1)) =
% max(X1, Y1) - min(X0, Y0).
%
% This version is more efficient since it does not create a
% temporary interval.
%
func bounding_region_size(K, K) = float
].
%--------------------------------------------------%
% Initialize an empty rtree.
%
:- func init = (rtree(K, V)::uo) is det <= region(K).
% Succeeds if-and-only-if the given rtree is empty.
%
:- pred is_empty(rtree(K, V)::in) is semidet.
% Insert a new key and corresponding value into an rtree.
%
:- func insert(K, V, rtree(K, V)) = rtree(K, V) <= region(K).
:- pred insert(K::in, V::in, rtree(K, V)::in, rtree(K, V)::out)
is det <= region(K).
% Delete a key-value pair from an rtree.
% Assumes that K is either the key for V, or is contained in the key
% for V.
%
% Fails if the key-value pair is not in the tree.
%
:- pred delete(K::in, V::in, rtree(K, V)::in, rtree(K, V)::out)
is semidet <= region(K).
% Search for all values with keys that intersect the query key.
%
:- func search_intersects(rtree(K, V), K) = list(V) <= region(K).
% Search for all values with keys that contain the query key.
%
:- func search_contains(rtree(K, V), K) = list(V) <= region(K).
% search_general(KTest, VTest, T) = V.
%
% Search for all values V with associated keys K that satisfy
% KTest(K) /\ VTest(V). The search assumes that for all K1, K2
% such that K1 contains K2, then if KTest(K2) holds we have that
% KTest(K1) also holds.
%
% We have that:
%
% search_intersects(T, K, Vs)
% <=> search_general(intersects(K), true, T, Vs)
%
% search_contains(T, K, Vs)
% <=> search_general(contains(K), true, T, Vs)
%
:- func search_general(pred(K)::in(pred(in) is semidet),
pred(V)::in(pred(in) is semidet), rtree(K, V)::in) = (list(V)::out)
is det.
% search_first(KTest, VTest, Max, T, V, L).
%
% Search for a value V with associated key K such that
% KTest(K, _) /\ VTest(V, L) is satisfied and there does not exist a
% V' with K' such that KTest(K', _) /\ VTest(V', L') /\ (L' < L) is
% satisfied. Fail if no such key-value pair exists.
%
% The search assumes that for all K1, K2 such that
% K1 contains K2, then if KTest(K2, L2) holds we have that
% KTest(K1, L1) holds with L2 >= L1.
%
% If there exist multiple key-value pairs that satisfy the above
% conditions, then one of the candidates is chosen arbitrarily.
%
:- pred search_first(pred(K, L), pred(V, L), rtree(K, V), L, V, L).
:- mode search_first(in(pred(in, out) is semidet),
in(pred(in, out) is semidet), in, in, out, out) is semidet.
% search_general_fold(KTest, VPred, T, !A).
%
% Apply accumulator VPred to each key-value pair K-V that satisfies
% KTest(K). The same assumptions for KTest from search_general apply
% here.
%
:- pred search_general_fold(pred(K), pred(K, V, A, A), rtree(K, V),
A, A).
:- mode search_general_fold(in(pred(in) is semidet),
in(pred(in, in, in, out) is det), in, in, out) is det.
:- mode search_general_fold(in(pred(in) is semidet),
in(pred(in, in, di, uo) is det), in, di, uo) is det.
% Perform a traversal of the rtree, applying an accumulator predicate
% for each key-value pair.
%
:- pred fold(pred(K, V, A, A), rtree(K, V), A, A).
:- mode fold(in(pred(in, in, in, out) is det), in, in, out) is det.
:- mode fold(in(pred(in, in, di, uo) is det), in, di, uo) is det.
:- mode fold(in(pred(in, in, in, out) is semidet), in, in, out) is semidet.
% Apply a transformation predicate to all the values in an rtree.
%
:- pred map_values(pred(K, V, W), rtree(K, V), rtree(K, W)).
:- mode map_values(in(pred(in, in, out) is det), in, out) is det.
:- mode map_values(in(pred(in, in, out) is semidet), in, out) is semidet.
%--------------------------------------------------%
%
% Pre-defined regions.
%
% An interval type represented as interval(Min, Max).
%
:- type interval
---> interval(float, float).
% A 2D axis aligned box represented as box(XMin, XMax, YMin, YMax).
%
:- type box
---> box(float, float, float, float).
% A 3D axis aligned box represented as
% box(XMin, XMax, YMin, YMax, ZMin, ZMax).
%
:- type box3d
---> box3d(float, float, float, float, float, float).
:- instance region(interval).
:- instance region(box).
:- instance region(box3d).
%--------------------------------------------------%
%--------------------------------------------------%