-- 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 07
-- Authors: Mathieu Basille, Ferdinando Urbano, Pierre Racine, Valerio Capecchi, Francesca Cagnacci
-- 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).

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


-- You explore the range data type
SELECT int4range(1, 10);
SELECT daterange('2013-03-20', '2013-09-22', '[]');
SELECT '[2013-03-20, 2013-09-22]'::daterange;
SELECT '[2013-03-20 11:01:55, 2013-09-22 20:44:08)'::tsrange;
SELECT '[2013-03-20, 2013-09-22]'::daterange @> now()::date AS in_range;
SELECT
  '[2013-03-20, 2013-09-22]'::daterange = daterange('2013-03-20', '2013-09-22', '[]') AS equal_range;
SELECT 
  '[2013-03-20, 2013-09-22]'::daterange + '[2013-06-01, 2014-01-01)'::daterange AS union_range;
SELECT 
  '[2013-03-20, 2013-09-22]'::daterange * '[2013-06-01, 2014-01-01)'::daterange AS intersection_range;
SELECT 
  '[2013-03-20, 2013-09-22]'::daterange && '[2013-06-01, 2014-01-01)'::daterange AS overlap_range;
SELECT 
  '[2013-01-01,)'::daterange;
SELECT 
  '[2013-01-01,)'::daterange @> now()::date AS after_2013;
  
-- You create a table to store NDVI values
CREATE TABLE env_data.ndvi_modis(
  rid serial NOT NULL, 
  rast raster, 
  filename text,
  acquisition_range daterange,
  CONSTRAINT ndvi_modis_pkey
    PRIMARY KEY (rid));

CREATE INDEX ndvi_modis_wkb_rast_idx 
  ON env_data.ndvi_modis 
  USING GIST (ST_ConvexHull(rast));

COMMENT ON TABLE env_data.ndvi_modis
IS 'Table that stores values of smoothed MODIS NDVI (16-day periods).';

-- You import the NDVI images (from a command prompt)
-- C:\Program Files\PostgreSQL\9.2\bin\raster2pgsql.exe -a -C -F -M -s 4326 -t 20x20 -N -3000 C:\tracking_db\data\env_data\raster\raster_ts\*.tif env_data.ndvi_modis | psql -p 5432 -d gps_tracking_db -U postgres  

-- You check NDVI table metadata
SELECT 
  r_table_schema AS schema, 
  r_table_name AS table, 
  srid, 
  nodata_values AS nodata
FROM raster_columns
WHERE r_table_name = 'ndvi_modis';

-- You extract the reference time range from the image name
SELECT filename, 
  (substring(filename FROM 12 FOR 4) || '-' || 
    substring(filename FROM 17 FOR 2) || '-' || 
    substring(filename FROM 20 FOR 2))::date AS start
FROM env_data.ndvi_modis LIMIT 1;

SELECT filename, 
  (substring(filename FROM 12 FOR 4) || '-' || 
    substring(filename FROM 17 FOR 2) || '-' ||
    substring(filename FROM 20 FOR 2))::date + 16 AS end
FROM env_data.ndvi_modis LIMIT 1;

UPDATE env_data.ndvi_modis
SET acquisition_range = daterange(
  (substring(filename FROM 12 FOR 4) || '-' || 
    substring(filename FROM 17 FOR 2) || '-' || 
    substring(filename FROM 20 FOR 2))::date,
  LEAST((substring(filename FROM 12 FOR 4) || '-' || 
      substring(filename FROM 17 FOR 2) || '-' || 
      substring(filename FROM 20 FOR 2))::date + 16,
    (substring(filename FROM 12 FOR 4)::integer + 1 
      || '-' || '01' || '-' || '01')::date));

CREATE INDEX ndvi_modis_acquisition_range_idx 
ON env_data.ndvi_modis (acquisition_range);

SELECT rid, filename, acquisition_range
FROM env_data.ndvi_modis
WHERE acquisition_range @> '2008-03-01'::date
LIMIT 10;

CREATE OR REPLACE FUNCTION tools.ndvi_acquisition_range_update()
RETURNS trigger AS
$BODY$
BEGIN
  NEW.acquisition_range = daterange(
    (substring(NEW.filename FROM 12 FOR 4) || '-' || 
      substring(NEW.filename FROM 17 FOR 2) || '-' || 
      substring(NEW.filename FROM 20 FOR 2))::date,
    LEAST((substring(NEW.filename FROM 12 FOR 4) || '-' || 
        substring(NEW.filename FROM 17 FOR 2) || '-' || 
        substring(NEW.filename FROM 20 FOR 2))::date + 16,
      (substring(NEW.filename FROM 12 FOR 4)::integer + 1 
        || '-' || '01' || '-' || '01')::date));
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
COMMENT ON FUNCTION tools.ndvi_acquisition_range_update() 
IS 'This function is raised whenever a new record is inserted into the MODIS NDVI time series table in order to define the date range. The acquisition_range value is derived from the original filename (that has the structure MODIS_NDVI_YYYY_MM_DD.tif)';

CREATE TRIGGER update_ndvi_acquisition_range
BEFORE INSERT ON env_data.ndvi_modis
  FOR EACH ROW EXECUTE PROCEDURE tools.ndvi_acquisition_range_update();

-- You intersect NDVI images and GPS positions with different approaches
SELECT 
  rid,
  acquisition_range,
  ST_Value(rast, ST_SetSRID(ST_MakePoint(11, 46), 4326)) / 10000 
    AS ndvi
FROM env_data.ndvi_modis
WHERE ST_Intersects(ST_SetSRID(ST_MakePoint(11, 46), 4326), rast)
  AND acquisition_range && '[2005-01-01,2005-12-31]'::daterange
ORDER BY acquisition_range;

SELECT 
  animals_id AS ani_id,
  ST_X(geom) AS x,
  ST_Y(geom) AS y,
  acquisition_time, 
  ST_Value(rast, geom) / 10000 AS ndvi
FROM main.gps_data_animals, env_data.ndvi_modis
WHERE ST_Intersects(geom, rast)
  AND acquisition_range @> acquisition_time::date
ORDER BY ani_id, acquisition_time
LIMIT 10;

WITH 
  mcp AS (
    SELECT 
      animals_id,
      min(acquisition_time) AS start_time,
      max(acquisition_time) AS end_time,
      ST_ConvexHull(ST_Collect(geom)) AS geom
    FROM main.gps_data_animals
    WHERE acquisition_time >= '2005-12-21'::date 
      AND acquisition_time < '2006-03-21'::date
    GROUP BY animals_id),
  ndvi_winter_mcp AS (
    SELECT 
      animals_id, 
      start_time, 
      end_time, 
      ST_SummaryStats(ST_Clip(ST_Union(rast), geom)) AS ss, 
      acquisition_range
    FROM mcp, env_data.ndvi_modis
    WHERE ST_Intersects(geom, rast) 
      AND lower(acquisition_range) >= '2005-12-21'::date 
      AND lower(acquisition_range) < '2006-03-21'::date
    GROUP BY animals_id, start_time, end_time, geom,
      acquisition_range),
  ndvi_winter_locs AS (
    SELECT
      animals_id,
      avg(ST_Value(rast, geom)) / 10000 AS mean
    FROM main.gps_data_animals, env_data.ndvi_modis
    WHERE acquisition_time >= '2005-12-21'::date 
      AND acquisition_time < '2006-03-21'::date
      AND ST_Intersects(geom, rast)
      AND acquisition_range @> acquisition_time::date
    GROUP BY animals_id)
SELECT 
  m.animals_id AS id,
  m.start_time::date,
  m.end_time::date, 
  avg((m.ss).mean) / 10000 AS mean_mcp,
  l.mean AS mean_loc
FROM ndvi_winter_mcp AS m, ndvi_winter_locs AS l
WHERE m.animals_id = l.animals_id
GROUP BY m.animals_id, m.start_time, m.end_time, l.mean
ORDER BY m.animals_id;

-- You create an automatic procedure to update the NDVI value associated to each location
ALTER TABLE main.gps_data_animals 
ADD COLUMN ndvi_modis integer;

UPDATE 
  main.gps_data_animals
SET 
  ndvi_modis = ST_Value(rast, geom)
FROM 
  env_data.ndvi_modis 
WHERE 
  ST_Intersects(geom, rast) AND 
  acquisition_range @> acquisition_time::date AND
  ndvi_modis IS NULL;

SELECT 
  gps_data_animals_id AS id, acquisition_time, 
  ndvi_modis / 10000.0 AS ndvi
FROM 
  main.gps_data_animals 
WHERE 
  geom IS NOT NULL
ORDER BY
  acquisition_time
LIMIT 10;

CREATE OR REPLACE FUNCTION tools.new_gps_data_animals()
RETURNS trigger AS
$BODY$
DECLARE 
  thegeom geometry;
  thedate date;
BEGIN
IF NEW.longitude IS NOT NULL AND NEW.latitude IS NOT NULL THEN
  thegeom = ST_SetSRID(ST_MakePoint(NEW.longitude, NEW.latitude), 4326);
  thedate = NEW.acquisition_time::date;
  NEW.geom = thegeom;
  NEW.pro_com = 
    (SELECT pro_com::integer 
    FROM env_data.adm_boundaries 
    WHERE ST_Intersects(geom, thegeom)); 
  NEW.corine_land_cover_code = 
    (SELECT ST_Value(rast,ST_Transform(thegeom, 3035)) 
    FROM env_data.corine_land_cover 
    WHERE ST_Intersects(ST_Transform(thegeom, 3035), rast));
  NEW.altitude_srtm = 
    (SELECT ST_Value(rast, thegeom) 
    FROM env_data.srtm_dem 
    WHERE ST_Intersects(thegeom, rast));
  NEW.station_id = 
    (SELECT station_id::integer 
    FROM env_data.meteo_stations 
    ORDER BY ST_Distance_Spheroid(thegeom, geom, 'SPHEROID["WGS 84",6378137,298.257223563]') 
    LIMIT 1);
  NEW.roads_dist = 
    (SELECT ST_Distance(thegeom::geography, geom::geography)::integer 
    FROM env_data.roads 
    ORDER BY ST_distance(thegeom::geography, geom::geography) 
    LIMIT 1);
  NEW.ndvi_modis = 
    (SELECT ST_Value(rast, thegeom)
    FROM env_data.ndvi_modis 
    WHERE ST_Intersects(thegeom, rast)
      AND acquisition_range @> thedate);
  END IF;
RETURN NEW;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
COMMENT ON FUNCTION tools.new_gps_data_animals() 
IS 'When called by the trigger insert_gps_positions (raised whenever a new position is uploaded into gps_data_animals) this function gets the longitude and latitude values and sets the geometry field accordingly, computing a set of derived environmental information calculated intersecting or relating the position with the environmental ancillary layers.';

CREATE OR REPLACE FUNCTION tools.ndvi_intersection_update()
RETURNS trigger AS
$BODY$
BEGIN
  UPDATE main.gps_data_animals
  SET ndvi_modis = 
    (SELECT ST_Value(NEW.rast, geom)
      FROM env_data.ndvi_modis 
      WHERE ST_Intersects(geom, NEW.rast)
      AND NEW.acquisition_range @> NEW.acquisition_time::date)
  WHERE ST_Intersects(geom, NEW.rast) AND
    NEW.acquisition_range @> acquisition_time::date AND
    ndvi.modis IS NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
COMMENT ON FUNCTION tools.ndvi_intersection_update ()
IS 'When new NDVI data is added, the ndvi_modis field of main.gps_data_animals is updated.';

CREATE TRIGGER update_ndvi_intersection
AFTER INSERT ON env_data.ndvi_modis
  FOR EACH ROW EXECUTE PROCEDURE tools.ndvi_intersection_update();