-- SPATIAL DATABASE FOR GPS WILDLIFE TRACKING DATA, F. Urbano and F. Cagnacci (eds.)
-- DOI: 10.1007/978-3-319-03743-1_4, Springer International Publishing Switzerland 2014

-- Code presented in Chapter 11
-- Authors: Mathieu Basille, Ferdinando Urbano, Joe Conway
-- Version 1.0

-- The code in this book is free. You can copy, modify and distribute non-trivial part of the code 
-- with no restrictions, according to the terms of the Creative Commons CC0 1.0 licence
-- (https://creativecommons.org/publicdomain/zero/1.0/). 
-- Nevertheless, the acknowledgement of the authorship is appreciated.

-- Note: to run this code you need the database developed in the previous chapters (2,3,4,5,6,7,8,9).

-- The test data set is available in the Extra Material page of the book (http://extras.springer.com/)


-- Load the Pl/R extension
CREATE EXTENSION plr;
-- Test the verson installed
SELECT * FROM plr_version();

-- Create a simple function using Pl/R
CREATE OR REPLACE FUNCTION tools.plr_fn ()
RETURNS float8 AS
$BODY$
  x <- 10
  4/3*pi*x^3
$BODY$
LANGUAGE 'plr';

SELECT tools.plr_fn ();

-- Create a function to compute logarithms and test it
CREATE OR REPLACE FUNCTION tools.r_log(float8, float8)
RETURNS float AS
$BODY$
  log(arg1, arg2)
$BODY$
LANGUAGE 'plr';

SELECT 
  area, log(area), tools.r_log(area, 10),
  ln(area), tools.r_log(area, exp(1))
FROM analysis.home_ranges_mcp
WHERE description = 'test all animals at 0.9';

-- Calculate the median with Pl/R
CREATE OR REPLACE FUNCTION tools.median(float8[])
RETURNS float AS
$BODY$
  median(arg1, na.rm = TRUE)
$BODY$
LANGUAGE 'plr';

CREATE AGGREGATE tools.median (float8)
(
  sfunc = plr_array_accum,
  stype = float8[],
  finalfunc = tools.median
);

SELECT count(area), avg(area), tools.median(area)
FROM analysis.home_ranges_mcp
WHERE description = 'test all animals at 0.9';

SELECT 
  animals_id, avg(altitude_srtm), tools.median(altitude_srtm),
  tools.median(altitude_srtm) - avg(altitude_srtm) AS diff
FROM main.gps_data_animals
WHERE animals_id != 6
  AND gps_validity_code = 1
GROUP BY animals_id
ORDER BY animals_id;

-- Calculate quantiles with Pl/R

CREATE TYPE tools.array_val AS (arr float8[], val float8);

CREATE OR REPLACE FUNCTION tools.plr_array_val_append(
  array_val tools.array_val, new_val float8, keep_val float8)
RETURNS tools.array_val CALLED ON NULL INPUT AS
$BODY$
  DECLARE
    arr float8[];
    out record;
  BEGIN
    IF array_val IS NULL THEN
      arr := ARRAY[new_val];
    ELSE
      arr := array_val.arr || new_val;
    END IF;
    out = row(arr, keep_val)::tools.array_val;
    RETURN out;
  END;
$BODY$ 
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION tools.quantile(tools.array_val)
RETURNS float AS
$BODY$
  quantile(unlist(arg1$arr), probs = arg1$val, na.rm = TRUE)
$BODY$
LANGUAGE 'plr';

CREATE AGGREGATE tools.quantile (float8, float8) 
(
  sfunc = tools.plr_array_val_append,
  stype = tools.array_val,
  finalfunc = tools.quantile
);

SELECT 
  count(area), avg(area), tools.median(area), 
  tools.quantile(area, 0.5) AS quant50, 
  tools.quantile(area, 0.1) AS quant10, 
  tools.quantile(area, 0.9) AS quant90
FROM analysis.home_ranges_mcp
WHERE description = 'test all animals at 0.9';

SELECT 
  animals_id, avg(altitude_srtm), tools.median(altitude_srtm),
  tools.quantile(altitude_srtm, 0.05) AS quant05, 
  tools.quantile(altitude_srtm, 0.95) AS quant95
FROM main.gps_data_animals
WHERE animals_id != 6
GROUP BY animals_id
ORDER BY animals_id;

-- Use Pl/R to caculate daylight
CREATE OR REPLACE FUNCTION tools.daylight(
  wkt text, 
  srid integer, 
  datetime timestamptz, 
  timezone text)
RETURNS text[] AS
$BODY$
  require(rgeos)
  require(maptools)
  require(rgdal)
  pt <- readWKT(wkt, p4s = CRS(paste0("+init=epsg:", srid)))
  dt <- as.POSIXct(substring(datetime, 1, 19), tz = timezone)
  sr <- sunriset(pt, dateTime = dt, direction = "sunrise",
      POSIXct.out = TRUE)$time
  ss <- sunriset(pt, dateTime = dt, direction = "sunset",
      POSIXct.out = TRUE)$time
  return(c(as.character(sr), as.character(ss)))
$BODY$
LANGUAGE 'plr';

SELECT tools.daylight('POINT(11.001 46.001)', 4326, 
  '2012-09-01'::timestamp, 'Europe/Rome');
  
CREATE OR REPLACE FUNCTION tools.is_daylight(
  wkt text, 
  srid integer, 
  datetime timestamptz, 
  timezone text)
RETURNS boolean AS
$BODY$
  require(rgeos)
  require(maptools)
  require(rgdal)
  pt <- readWKT(wkt, p4s = CRS(paste0("+init=epsg:", srid)))
  dt <- as.POSIXct(substring(datetime, 1, 19), tz = timezone)
  sr <- sunriset(pt, dateTime = dt, direction = "sunrise",
      POSIXct.out = TRUE)$time
  ss <- sunriset(pt, dateTime = dt, direction = "sunset",
      POSIXct.out = TRUE)$time
  return(ifelse(dt >= sr & dt < ss, TRUE, FALSE))
$BODY$
LANGUAGE 'plr';

SELECT tools.is_daylight('POINT(11.001 46.001)', 4326, 
  '2013-10-10 12:34:56'::timestamp, 'Europe/Rome');

WITH tmp AS (SELECT ('Europe/Rome')::text AS tz)
SELECT 
  ST_AsText(geom) AS location, 
  acquisition_time AT TIME ZONE tz AS acquisition_time,
  tools.is_daylight(ST_AsText(geom), ST_SRID(geom), acquisition_time 
    AT TIME ZONE tz, tz)
FROM main.gps_data_animals, tmp
WHERE gps_validity_code = 1
LIMIT 10;

-- Extend the home range concept with new tools (MCP)
CREATE TYPE tools.hr AS (percent int, wkt text);
CREATE OR REPLACE FUNCTION tools.mcp_r (wkt text, percent integer)
RETURNS SETOF tools.hr AS
$BODY$ 
  require(rgeos)
  require(adehabitatHR)
  geom <- readWKT(wkt)
  return(data.frame(percent = percent, wkt = sapply(percent, 
      function(x) writeWKT(mcp(geom, x)))))
$BODY$ 
LANGUAGE plr;

SELECT (tools.mcp_r(ST_AsText(ST_Collect(geom)), 90)).*
FROM main.gps_data_animals
WHERE gps_validity_code = 1
  AND animals_id = 1;

WITH 
  mcpr AS (
    SELECT 
      animals_id, 
      (tools.mcp_r(ST_AsText(ST_Collect(geom)), 90)).*
    FROM main.gps_data_animals
    WHERE gps_validity_code = 1
      AND animals_id <> 6
    GROUP BY animals_id)
SELECT 
  mcpr.animals_id, mcpr.percent,
  ST_Area(geography(wkt)) / 1000000 AS area_r, 
  mcp.area AS area_pg
FROM mcpr, analysis.home_ranges_mcp AS mcp
WHERE mcpr.animals_id = mcp.animals_id
  AND mcp.description = 'test all animals at 0.9'
GROUP BY mcpr.animals_id, mcpr.wkt, mcp.area, percent
ORDER BY mcpr.animals_id;
  
-- Extend the home range concept with new tools (kernelUD)
CREATE OR REPLACE FUNCTION tools.kernelud (wkt text, percent integer)
RETURNS SETOF tools.hr AS
$BODY$ 
  require(rgeos)
  require(adehabitatHR)
  geom <- readWKT(wkt)
  kud <- kernelUD(geom)
  return(data.frame(percent = percent, wkt = sapply(percent, 
      function(x) writeWKT(getverticeshr(kud, x)))))
$BODY$ 
LANGUAGE plr;

WITH tmp AS (SELECT unnest(ARRAY[50,90,95]) AS pc)
SELECT (tools.kernelud(ST_AsText(ST_Collect(geom)), pc)).*
FROM main.gps_data_animals, tmp
WHERE gps_validity_code = 1
  AND animals_id = 1
GROUP BY pc
ORDER BY pc;

CREATE TABLE analysis.home_ranges_kernelud(
  home_ranges_kernelud_id serial NOT NULL,
  animals_id integer NOT NULL,
  start_time timestamp with time zone NOT NULL,
  end_time timestamp with time zone NOT NULL,
  num_locations integer,
  area numeric(13,5),
  geom geometry (multipolygon, 4326),
  percentage double precision,
  insert_timestamp timestamp with time zone 
    DEFAULT now(),
  CONSTRAINT home_ranges_kernelud_pk 
    PRIMARY KEY (home_ranges_kernelud_id),
  CONSTRAINT home_ranges_kernelud_animals_fk 
    FOREIGN KEY (animals_id)
    REFERENCES main.animals (animals_id) MATCH SIMPLE
    ON UPDATE NO ACTION ON DELETE NO ACTION);
COMMENT ON TABLE analysis.home_ranges_kernelud
IS 'Table that stores the home range polygons derived from kernelUD. The area is computed in hectars.';

CREATE INDEX fki_home_ranges_kernelud_animals_fk
  ON analysis.home_ranges_kernelud
  USING btree (animals_id);
CREATE INDEX gist_home_ranges_kernelud_index
  ON analysis.home_ranges_kernelud
  USING gist (geom);
  
WITH 
  tmp AS (SELECT unnest(ARRAY[50,90,95]) AS pc),
  kud AS (
    SELECT 
      animals_id, 
      min(acquisition_time) AS start_time,
      max(acquisition_time) AS end_time,
      count(animals_id) AS num_locations,
      (tools.kernelud(ST_AsText(ST_Collect(geom)), pc)).*
    FROM main.gps_data_animals, tmp
    WHERE 
      gps_validity_code = 1 AND 
      animals_id <> 6
    GROUP BY animals_id,pc
    ORDER BY animals_id,pc)
INSERT INTO analysis.home_ranges_kernelud (animals_id, start_time, end_time, num_locations, area, geom, percentage)
SELECT
  animals_id, 
  start_time,
  end_time,
  num_locations,
  ST_Area(geography(wkt)) / 1000000, 
  ST_GeomFromText(wkt, 4326),
  percent / 100.0
FROM kud
ORDER BY animals_id, percent;

SELECT 
  mcp.animals_id AS ani_id, 
  mcp.area AS mcp_area, 
  kud.area AS kud_area, 
  ST_Area(geography(ST_Intersection(mcp.geom, kud.geom))) / 1000000 AS overlap,
  ST_Area(geography(ST_Intersection(mcp.geom, kud.geom))) / ST_Area(geography(ST_Union(mcp.geom, kud.geom))) AS over_prop
FROM 
  analysis.home_ranges_mcp AS mcp, 
  analysis.home_ranges_kernelud AS kud
WHERE 
  mcp.animals_id = kud.animals_id AND 
  mcp.percentage = kud.percentage AND 
  mcp.percentage = 0.9;

WITH 
  srid AS (
    SELECT tools.srid_utm(
      ST_X(ST_Centroid(ST_Collect(geom))),
      ST_Y(ST_Centroid(ST_Collect(geom)))) AS utm
    FROM main.gps_data_animals
    WHERE gps_validity_code = 1 AND animals_id = 1),
  kver AS (
    SELECT (tools.kernelud(ST_AsText(
      ST_Transform(ST_Collect(geom), srid.utm)), 90)).*
    FROM main.gps_data_animals, srid
    WHERE gps_validity_code = 1 AND animals_id = 1
    GROUP BY srid.utm)
SELECT
  kver.percent AS pc,
  ST_AsEWKT(
    ST_Transform(
      ST_GeomFromText(kver.wkt, srid.utm),
      4326)) AS ewkt
FROM kver, srid;