liquid_feedback_core

diff core.sql @ 529:96ee2db56bec

New PostgreSQL extension "pgLatLon" for geospatial operations and indexing in LiquidFeedback
author jbe
date Thu Aug 18 20:19:58 2016 +0200 (2016-08-18)
parents 3e28fd842354
children 5855ff9e5c8f
line diff
     1.1 --- a/core.sql	Sun May 29 20:42:40 2016 +0200
     1.2 +++ b/core.sql	Thu Aug 18 20:19:58 2016 +0200
     1.3 @@ -3,6 +3,8 @@
     1.4  
     1.5  BEGIN;
     1.6  
     1.7 +CREATE EXTENSION latlon;  -- load pgLatLon extenstion
     1.8 +
     1.9  CREATE VIEW "liquid_feedback_version" AS
    1.10    SELECT * FROM (VALUES ('4.0.0', 4, 0, 0))
    1.11    AS "subquery"("string", "major", "minor", "revision");
    1.12 @@ -46,586 +48,6 @@
    1.13  
    1.14  
    1.15  
    1.16 ------------------------------------------------
    1.17 --- spatial indexing and distance calculation --
    1.18 ------------------------------------------------
    1.19 -
    1.20 -
    1.21 -CREATE DOMAIN "geoindex" AS point CHECK (VALUE <@ '((-1,-1),(1,1))'::box);
    1.22 -
    1.23 -COMMENT ON DOMAIN "geoindex" IS 'Data type used for indexing geographical coordinates';
    1.24 -
    1.25 -
    1.26 -CREATE FUNCTION "geoindex"
    1.27 -  ( "latitude_p"  FLOAT,
    1.28 -    "longitude_p" FLOAT )
    1.29 -  RETURNS "geoindex"
    1.30 -  LANGUAGE sql IMMUTABLE STRICT AS $$
    1.31 -    SELECT point("latitude_p" / 90, "longitude_p" / 180)::"geoindex"
    1.32 -  $$;
    1.33 -
    1.34 -COMMENT ON FUNCTION "geoindex"
    1.35 -  (FLOAT, FLOAT)
    1.36 -  IS 'Transforms coordinates given as latitude (from -90 to 90) and logitude (from -180 to 180) into a data type suitable for a quadtree index';
    1.37 -
    1.38 -
    1.39 -CREATE TYPE "geosearch" AS (
    1.40 -        "lat"           FLOAT,
    1.41 -        "lon"           FLOAT,
    1.42 -        "c3"            FLOAT,
    1.43 -        "c2"            FLOAT,
    1.44 -        "c1"            FLOAT,
    1.45 -        "c0"            FLOAT,
    1.46 -        "dist"          FLOAT,
    1.47 -        "bbox1"         BOX,
    1.48 -        "bbox2"         BOX );
    1.49 -
    1.50 -COMMENT ON TYPE "geosearch" IS 'Structure returned by function "geosearch" holding one or two bounding boxes ("bbox1" and optionally "bbox2") for index lookup as well as the starting point (i.e. center point given by "lat" and "lon") and coefficients ("c3" through "c0") for distance approximation';
    1.51 -
    1.52 -COMMENT ON COLUMN "geosearch"."lat"   IS 'Latitude of center of search area';
    1.53 -COMMENT ON COLUMN "geosearch"."lon"   IS 'Longitude of center of search area';
    1.54 -COMMENT ON COLUMN "geosearch"."c3"    IS 'Coefficient for distance calculation';
    1.55 -COMMENT ON COLUMN "geosearch"."c2"    IS 'Coefficient for distance calculation';
    1.56 -COMMENT ON COLUMN "geosearch"."c1"    IS 'Coefficient for distance calculation';
    1.57 -COMMENT ON COLUMN "geosearch"."c0"    IS 'Coefficient for distance calculation';
    1.58 -COMMENT ON COLUMN "geosearch"."bbox1" IS 'Bounding box suitable for use with output of "geoindex" function';
    1.59 -COMMENT ON COLUMN "geosearch"."bbox2" IS 'Second bounding box (see column "bbox1") set if search crosses the 180th meridian';
    1.60 -
    1.61 -
    1.62 -CREATE FUNCTION "geosearch"
    1.63 -  ( "latitude_p"  FLOAT,
    1.64 -    "longitude_p" FLOAT )
    1.65 -  RETURNS "geosearch"
    1.66 -  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
    1.67 -    DECLARE
    1.68 -      -- constants:
    1.69 -      -- (with a = radius at equator, and b = radius at pole)
    1.70 -      "degrad"   FLOAT := 0.017453292519943295;    -- pi / 180
    1.71 -      "degrad2"  FLOAT := 0.00030461741978670857;  -- (pi / 180)^2
    1.72 -      "a2"       FLOAT := 40680631590769;          -- a^2
    1.73 -      "eps2"     FLOAT := 0.006694379990141316;    -- (a^2 - b^2) / a^2
    1.74 -      "subeps2"  FLOAT := 0.9933056200098587;      -- 1 - "eps2"   
    1.75 -      "subeps22" FLOAT := 0.9866560547431698;      -- (1 - "eps2")^2
    1.76 -      -- latitude in radians:
    1.77 -      "phi0rad"  FLOAT := "degrad" * "latitude_p";
    1.78 -      -- helper variables:
    1.79 -      "sinp0"    FLOAT;  -- sin("phi0rad")
    1.80 -      "cosp0"    FLOAT;  -- cos("phi0rad")
    1.81 -      "sinp02"   FLOAT;  -- sin("phi0rad")^2
    1.82 -      "e2sinp02" FLOAT;  -- "eps2" * sin("phi0rad")^2
    1.83 -      "subsin"   FLOAT;  -- 1 - sin("phi0rad")^2
    1.84 -      "subse"    FLOAT;  -- 1 - "eps2" * sin("phi0rad")^2
    1.85 -      "subse2"   FLOAT;  -- ("subse")^2
    1.86 -      "t20"      FLOAT;  -- taylor coefficient for (delta latitude)^2
    1.87 -      "t02"      FLOAT;  -- taylor coefficient for (delta longitude)^2 / "t20"
    1.88 -      "t12"      FLOAT;  -- taylor coefficient for (d lat)^1*(d lon)^2 / "t20"
    1.89 -      "t22"      FLOAT;  -- taylor coefficient for (d lat)^2*(d lon)^2 / "t20"
    1.90 -      -- result:
    1.91 -      "res"      "geosearch";
    1.92 -    BEGIN
    1.93 -      PERFORM "geoindex"("latitude_p", "longitude_p");  -- check bounds
    1.94 -      "sinp0"    := sin("phi0rad");
    1.95 -      "cosp0"    := cos("phi0rad");
    1.96 -      "sinp02"   := "sinp0" * "sinp0";
    1.97 -      "e2sinp02" := "eps2" * "sinp02";
    1.98 -      "subsin"   := 1 - "sinp02";
    1.99 -      "subse"    := 1 - "e2sinp02";
   1.100 -      "subse2"   := "subse" * "subse";
   1.101 -      "t20"      := "a2" * "subeps22" / ("subse" * "subse2");
   1.102 -      "t02"      := "subsin" * "subse2" / "subeps22";
   1.103 -      "t12"      := -"sinp0" * "cosp0" * "subse" / "subeps2";
   1.104 -      "t22"      := -"subsin" * (0.5+"e2sinp02") / "subeps2";
   1.105 -      "res"."lat" := "latitude_p";
   1.106 -      "res"."lon" := "longitude_p";
   1.107 -      "res"."c3"  := "degrad2" * "t20";
   1.108 -      "res"."c2"  := "degrad2" * "t22";
   1.109 -      "res"."c1"  := "degrad" * ("t12" - 2 * "t22" * "phi0rad");
   1.110 -      "res"."c0"  := ("t22" * "phi0rad" - "t12") * "phi0rad" + "t02";
   1.111 -      "res"."dist"  := 'Infinity'::FLOAT;
   1.112 -      "res"."bbox1" := box(point(1, 1), point(-1, -1));
   1.113 -      "res"."bbox2" := NULL;
   1.114 -      RETURN "res";
   1.115 -    END;
   1.116 -  $$;
   1.117 -
   1.118 -COMMENT ON FUNCTION "geosearch"
   1.119 -  ( FLOAT, FLOAT )
   1.120 -  IS 'Takes latitude and longitude (in degrees), and returns a "geosearch" structure suitable for distance calculation; NOTE: use function with same name but three arguments, i.e. "geosearch"(FLOAT, FLOAT, FLOAT), for searches with limited radius';
   1.121 -
   1.122 -
   1.123 -CREATE FUNCTION "geosearch"
   1.124 -  ( "latitude_p"  FLOAT,
   1.125 -    "longitude_p" FLOAT,
   1.126 -    "radius_p"    FLOAT )
   1.127 -  RETURNS "geosearch"
   1.128 -  LANGUAGE plpgsql IMMUTABLE AS $$
   1.129 -    DECLARE
   1.130 -      -- constants:
   1.131 -      "philimit" FLOAT := 84;    -- maximum latitude (needs to be less than 90)
   1.132 -      "margin"   FLOAT := 1.0001;  -- slightly increase search area
   1.133 -      -- NOTE: search area is increased by "margin", search radius gets
   1.134 -      --       increased by sqrt("margin"); choose a value > 1.0 to avoid
   1.135 -      --       floating point errors
   1.136 -      "fixzero"  FLOAT := 0.0001;  -- small value > 0 to fix singularity
   1.137 -      -- result:
   1.138 -      "res"      "geosearch";
   1.139 -      -- helper variables:
   1.140 -      "rc3mar"   FLOAT;  -- ("radius_p")^2 / "c3", multiplied with "margin"
   1.141 -      "subc02"   FLOAT;  -- 2 * ("c0" - "c2" * "rc3mar")
   1.142 -      "b4"       FLOAT;  -- ("c2")^2
   1.143 -      "b3"       FLOAT;  -- 2 * "c1" * "c2"
   1.144 -      "b2"       FLOAT;  -- ("c1")^2 + "subc02" * "c2"
   1.145 -      "b1"       FLOAT;  -- "subc02" * "c1"
   1.146 -      "b0"       FLOAT;  -- ("c2")^2                   * ("rc3mar")^2 +
   1.147 -                         -- 2 * "c0" * "c2" - ("c1")^2 * ("rc3mar")^1 +
   1.148 -                         -- ("c0")^2                   * ("rc3mar")^0
   1.149 -      "sqrtval"  FLOAT;  -- "b4" * ("latitude_p")^4 +
   1.150 -                         -- "b3" * ("latitude_p")^3 +
   1.151 -                         -- "b2" * ("latitude_p")^2 +
   1.152 -                         -- "b1" * ("latitude_p")^1 +
   1.153 -                         -- "b0" * ("latitude_p")^0
   1.154 -      "phic"     FLOAT;  -- ( "c2" * (("latitude_p")^2 - "rc3mar") - "c0"
   1.155 -                         --   + sqrt("sqrtval")
   1.156 -                         -- ) / ( 2 * "c2" * "latitude_p" + "c1" )
   1.157 -      "dlat"     FLOAT;  -- delta latitude in degrees = sqrt("rc3mar")
   1.158 -      "dlon2"    FLOAT;  -- square of delta longitude
   1.159 -      "dlon"     FLOAT;  -- delta longitude in degrees =
   1.160 -                         -- sqrt(
   1.161 -                         --   ( "rc3mar" - ("phic" - "latitude_p")^2 ) /
   1.162 -                         --   ( "c2" * ("phic")^2 +
   1.163 -                         --     "c1" * ("phic")^1 +
   1.164 -                         --     "c0" )
   1.165 -                         -- )
   1.166 -      "lat_min"  FLOAT;
   1.167 -      "lat_max"  FLOAT;
   1.168 -      "lon_min"  FLOAT;
   1.169 -      "lon_max"  FLOAT;
   1.170 -    BEGIN
   1.171 -      "res" := "geosearch"("latitude_p", "longitude_p");
   1.172 -      IF "res" ISNULL THEN RETURN NULL; END IF;
   1.173 -      IF "radius_p" ISNULL OR "radius_p" = 'Infinity'::FLOAT THEN
   1.174 -        RETURN "res";
   1.175 -      END IF;
   1.176 -      "res"."dist" := "radius_p";
   1.177 -      "rc3mar"  := "margin" * "radius_p" * "radius_p" / "res"."c3";
   1.178 -      "subc02"  := 2 * ("res"."c0" - "res"."c2" * "rc3mar");
   1.179 -      "b4"      := "res"."c2" * "res"."c2";
   1.180 -      "b3"      := 2 * "res"."c1" * "res"."c2";
   1.181 -      "b2"      := "res"."c1" * "res"."c1" + "subc02" * "res"."c2";
   1.182 -      "b1"      := "subc02" * "res"."c1";
   1.183 -      "b0"      := ( "b4" * "rc3mar" +
   1.184 -                     2 * "res"."c0" * "res"."c2" - "res"."c1" * "res"."c1"
   1.185 -                   ) * "rc3mar" + "res"."c0" * "res"."c0";
   1.186 -      "sqrtval" := ( ( ( ( "b4"
   1.187 -                         ) * "latitude_p" + "b3"
   1.188 -                       ) * "latitude_p" + "b2"
   1.189 -                     ) * "latitude_p" + "b1"
   1.190 -                   ) * "latitude_p" + "b0";
   1.191 -      IF "sqrtval" < 0 THEN
   1.192 -        IF "latitude_p" >= 0 THEN "phic" := "philimit";
   1.193 -        ELSE "phic" := -"philimit"; END IF;
   1.194 -      ELSE
   1.195 -        IF abs("latitude_p") <= "fixzero" THEN
   1.196 -          "phic" := "latitude_p";
   1.197 -        ELSE
   1.198 -          "phic" := (
   1.199 -            "res"."c2" * ("latitude_p" * "latitude_p" - "rc3mar")
   1.200 -            - "res"."c0" + sqrt("sqrtval")
   1.201 -          ) / (
   1.202 -            2 * "res"."c2" * "latitude_p" + "res"."c1"
   1.203 -          );
   1.204 -          IF "phic" > "philimit" THEN "phic" := "philimit";
   1.205 -          ELSIF "phic" < -"philimit" THEN "phic" := -"philimit"; END IF;
   1.206 -        END IF;
   1.207 -      END IF;
   1.208 -      "dlat" := sqrt("rc3mar");
   1.209 -      "dlon2" := (
   1.210 -        ( "rc3mar" - ("phic" - "latitude_p")^2 ) /
   1.211 -        ( ( "res"."c2" * "phic" + "res"."c1" ) * "phic" + "res"."c0" )
   1.212 -      );
   1.213 -      IF "dlon2" > 0 THEN "dlon" := sqrt("dlon2");
   1.214 -      ELSE "dlon" := 0; END IF;
   1.215 -      "lat_min" := "latitude_p" - "dlat";
   1.216 -      "lat_max" := "latitude_p" + "dlat";
   1.217 -      IF "lat_min" < -90 THEN "lat_min" := -90; END IF;
   1.218 -      IF "lat_max" >  90 THEN "lat_max" :=  90; END IF;
   1.219 -      "lon_min" := "longitude_p" - "dlon";
   1.220 -      "lon_max" := "longitude_p" + "dlon";
   1.221 -      IF "lon_min" < -180 THEN
   1.222 -        IF "lon_max" > 180 THEN
   1.223 -          "res"."bbox1" := box(
   1.224 -            "geoindex"("lat_min", -180),
   1.225 -            "geoindex"("lat_max", 180) );
   1.226 -          "res"."bbox2" := NULL;
   1.227 -        ELSE
   1.228 -          "res"."bbox1" := box(
   1.229 -            "geoindex"("lat_min", -180),
   1.230 -            "geoindex"("lat_max", "lon_max") );
   1.231 -          "res"."bbox2" := box(
   1.232 -            "geoindex"("lat_min", "lon_min" + 360),
   1.233 -            "geoindex"("lat_max", 180) );
   1.234 -        END IF;
   1.235 -      ELSIF "lon_max" > 180 THEN
   1.236 -        "res"."bbox1" := box(
   1.237 -          "geoindex"("lat_min", "lon_min"),
   1.238 -          "geoindex"("lat_max", 180) );
   1.239 -        "res"."bbox2" := box(
   1.240 -          "geoindex"("lat_min", -180),
   1.241 -          "geoindex"("lat_max", "lon_max" - 360) );
   1.242 -      ELSE
   1.243 -        "res"."bbox1" := box(
   1.244 -          "geoindex"("lat_min", "lon_min"),
   1.245 -          "geoindex"("lat_max", "lon_max") );
   1.246 -        "res"."bbox2" := NULL;
   1.247 -      END IF;
   1.248 -      RETURN "res";
   1.249 -    END;
   1.250 -  $$;
   1.251 -
   1.252 -COMMENT ON FUNCTION "geosearch"
   1.253 -  ( FLOAT, FLOAT, FLOAT )
   1.254 -  IS 'Takes latitude, longitude (both in degrees), and a radius (in meters), and returns a "geoindex" structure suitable for searching and distance calculation';
   1.255 -
   1.256 -
   1.257 -CREATE FUNCTION "geodist"
   1.258 -  ( "geosearch_p" "geosearch",
   1.259 -    "latitude_p"  FLOAT,
   1.260 -    "longitude_p" FLOAT )
   1.261 -  RETURNS FLOAT
   1.262 -  LANGUAGE sql IMMUTABLE AS $$
   1.263 -    SELECT sqrt(
   1.264 -      "geosearch_p"."c3" * (
   1.265 -        ("latitude_p" - "geosearch_p"."lat")^2 +
   1.266 -        ( ( "geosearch_p"."c2" * "latitude_p" + "geosearch_p"."c1" )
   1.267 -          * "latitude_p" + "geosearch_p"."c0" ) *
   1.268 -        ( "longitude_p" +
   1.269 -          CASE WHEN "longitude_p" - "geosearch_p"."lon" > 180
   1.270 -          THEN -360 ELSE
   1.271 -            CASE WHEN "longitude_p" - "geosearch_p"."lon" < -180
   1.272 -            THEN 360
   1.273 -            ELSE 0 END
   1.274 -          END - "geosearch_p"."lon"
   1.275 -        )^2
   1.276 -      )
   1.277 -    )
   1.278 -  $$;
   1.279 -
   1.280 -COMMENT ON FUNCTION "geodist"
   1.281 -  ( "geosearch", FLOAT, FLOAT )
   1.282 -  IS 'Takes a "geosearch" structure as well as geographical coordinates in degrees and returns the distance on earth in meters';
   1.283 -
   1.284 -
   1.285 -/* Usage of spatial indexing and distance calculation functions for points:
   1.286 -
   1.287 --- create table containing geographical coordinates in degrees:
   1.288 -CREATE TABLE "t1" (
   1.289 -        "id"            SERIAL4         PRIMARY KEY,
   1.290 -        "latitude"      NUMERIC         NOT NULL,
   1.291 -        "longitude"     NUMERIC         NOT NULL );
   1.292 -
   1.293 --- create an SP-GiST index (with default quad_point_ops operator class):
   1.294 -CREATE INDEX "t1_geoindex" ON "t1" USING spgist (("geoindex"("latitude", "longitude")));
   1.295 -
   1.296 --- return results within 10,000 meters of Brandenburg Gate:
   1.297 -SELECT "t1".*, "distance"
   1.298 -  FROM "t1"
   1.299 -  CROSS JOIN "geosearch"(52.5162746, 13.377704, 10000)
   1.300 -  CROSS JOIN LATERAL "geodist"("geosearch", "latitude", "longitude") AS "distance"
   1.301 -  WHERE (
   1.302 -    "geoindex"("latitude", "longitude") <@ "geosearch"."bbox1" OR
   1.303 -    "geoindex"("latitude", "longitude") <@ "geosearch"."bbox2" )
   1.304 -  AND "distance" <= "geosearch"."dist";
   1.305 -
   1.306 -*/
   1.307 -
   1.308 -
   1.309 -CREATE FUNCTION "polyindex"
   1.310 -  ( "point_p" POINT )
   1.311 -  RETURNS POLYGON
   1.312 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.313 -    SELECT "point_p"::TEXT::POLYGON;
   1.314 -  $$;
   1.315 -
   1.316 -COMMENT ON FUNCTION "polyindex"(POINT) IS 'Converts a point (with latitude in degrees as first value and longitude in degrees as second value) into a polygon suitable for GiST poly_ops indexing'; 
   1.317 -
   1.318 -
   1.319 -CREATE FUNCTION "polyindex"
   1.320 -  ( "latitude_p"  FLOAT,
   1.321 -    "longitude_p" FLOAT )
   1.322 -  RETURNS POLYGON
   1.323 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.324 -    SELECT "polyindex"(point("latitude_p", "longitude_p"))
   1.325 -  $$;
   1.326 -
   1.327 -COMMENT ON FUNCTION "polyindex"(POINT) IS 'Converts a point given as latitude and longitude in degrees into a polygon suitable for GiST poly_ops indexing'; 
   1.328 -
   1.329 -
   1.330 -CREATE FUNCTION "polyindex"
   1.331 -  ( "polygon_p" POLYGON )
   1.332 -  RETURNS POLYGON
   1.333 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.334 -    SELECT "polygon_p"
   1.335 -  $$;
   1.336 -
   1.337 -COMMENT ON FUNCTION "polyindex"(POLYGON) IS 'Does nothing but returning the polygon unchanged (for GiST poly_ops indexing)'; 
   1.338 -
   1.339 -
   1.340 -CREATE FUNCTION "polyindex"
   1.341 -  ( "box_p" BOX )
   1.342 -  RETURNS POLYGON
   1.343 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.344 -    SELECT "box_p"::POLYGON
   1.345 -  $$;
   1.346 -
   1.347 -COMMENT ON FUNCTION "polyindex"(POINT) IS 'Converts a box (with latitude in degrees as first value and longitude in degrees as second value in each point) into a polygon suitable for GiST poly_ops indexing';
   1.348 -
   1.349 -
   1.350 -CREATE FUNCTION "polyindex"
   1.351 -  ( "path_p" PATH )
   1.352 -  RETURNS POLYGON
   1.353 -  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
   1.354 -    DECLARE
   1.355 -      "match_v"  TEXT[];
   1.356 -      "points_v" TEXT[];
   1.357 -      "idx1"     INTEGER;
   1.358 -      "idx2"     INTEGER;
   1.359 -    BEGIN
   1.360 -      IF isclosed("path_p") THEN
   1.361 -        RETURN "path_p"::POLYGON;
   1.362 -      ELSE
   1.363 -        "points_v" := '{}';
   1.364 -        "idx1" := 0;
   1.365 -        FOR "match_v" IN
   1.366 -          SELECT regexp_matches("path_p"::TEXT, E'\\(([^()]*)\\)', 'g')
   1.367 -        LOOP
   1.368 -          "idx1" := "idx1" + 1;
   1.369 -          "points_v"["idx1"] := "match_v"[1];
   1.370 -        END LOOP;
   1.371 -        "idx2" := "idx1";
   1.372 -        LOOP
   1.373 -          EXIT WHEN "idx1" < 3;
   1.374 -          "idx1" := "idx1" - 1;
   1.375 -          "idx2" := "idx2" + 1;
   1.376 -          "points_v"["idx2"] := "points_v"["idx1"];
   1.377 -        END LOOP;
   1.378 -        RETURN array_to_string("points_v", ',')::POLYGON;
   1.379 -      END IF;
   1.380 -    END;
   1.381 -  $$;
   1.382 -
   1.383 -COMMENT ON FUNCTION "polyindex"(POINT) IS 'Converts a path (with latitude in degrees as first value and longitude in degrees as second value in each point) into a polygon suitable for GiST poly_ops indexing; NOTE: closed PATHs where isclosed(path)=TRUE represent filled polygons, use an open PATH with the last point equal to the first point (e.g. "[1,1,3,4,6,6,1,1]") for a non-filled structure';
   1.384 -
   1.385 -
   1.386 -CREATE FUNCTION "polyindex"
   1.387 -  ( "lseg_p" LSEG )
   1.388 -  RETURNS POLYGON
   1.389 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.390 -    SELECT "polyindex"("lseg_p"::TEXT::PATH)
   1.391 -  $$;
   1.392 -
   1.393 -COMMENT ON FUNCTION "polyindex"(POINT) IS 'Converts a line segment (with latitude in degrees as first value and longitude in degrees as second value in each point) into a polygon suitable for GiST poly_ops indexing';
   1.394 -
   1.395 -
   1.396 -CREATE FUNCTION "geoshift180"
   1.397 -  ( "point_p" POINT )
   1.398 -  RETURNS POINT
   1.399 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.400 -    SELECT CASE WHEN "point_p" <^ '0,0'::POINT THEN
   1.401 -      "point_p" + '0,360'::POINT
   1.402 -    ELSE
   1.403 -      "point_p" - '0,360'::POINT
   1.404 -    END 
   1.405 -  $$;
   1.406 -
   1.407 -COMMENT ON FUNCTION "geoshift180"(POINT) IS 'Returns a point (latitude and longitude in degrees) with its longitude translated by 360 degress to allow for searches crossing the 180th meridian';
   1.408 -
   1.409 -
   1.410 -CREATE FUNCTION "geoshift180"
   1.411 -  ( "polygon_p" POLYGON )
   1.412 -  RETURNS POLYGON
   1.413 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.414 -    SELECT CASE WHEN center("polygon_p"::CIRCLE) <^ '0,0'::POINT THEN
   1.415 -      ("polygon_p"::PATH + '0,360'::POINT)::POLYGON
   1.416 -    ELSE
   1.417 -      ("polygon_p"::PATH - '0,360'::POINT)::POLYGON
   1.418 -    END 
   1.419 -  $$;
   1.420 -
   1.421 -COMMENT ON FUNCTION "geoshift180"(POLYGON) IS 'Returns a polygon (latitude and longitude in degrees) with its longitude translated by 360 degress to allow for searches crossing the 180th meridian';
   1.422 -
   1.423 -
   1.424 -CREATE FUNCTION "polysearch"
   1.425 -  ( "geosearch_p" "geosearch",
   1.426 -    "steps_p"     INTEGER = 24 )
   1.427 -  RETURNS POLYGON
   1.428 -  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
   1.429 -    DECLARE
   1.430 -      "philimit"  FLOAT := 84;   -- maximum latitude (needs to be less than 90)
   1.431 -      "halfsteps" INTEGER;
   1.432 -      "step"      INTEGER := 0;
   1.433 -      "angle"     FLOAT;
   1.434 -      "dlat"      FLOAT := "geosearch_p"."dist" / sqrt("geosearch_p"."c3");
   1.435 -      "lat"       FLOAT;
   1.436 -      "dlon"      FLOAT;
   1.437 -      "coords"    TEXT[];
   1.438 -    BEGIN
   1.439 -      IF "steps_p" < 4 OR "steps_p" % 2 != 0 THEN
   1.440 -        RAISE EXCEPTION 'Invalid step count';
   1.441 -      END IF;
   1.442 -      "halfsteps" := "steps_p" / 2;
   1.443 -      LOOP
   1.444 -        "angle" := 2.0 * pi() * "step" / "steps_p";
   1.445 -        "lat"   := "geosearch_p"."lat" + "dlat" * cos("angle");
   1.446 -        IF    "lat" >  "philimit" THEN "lat" :=  "philimit";
   1.447 -        ELSIF "lat" < -"philimit" THEN "lat" := -"philimit";
   1.448 -        END IF;
   1.449 -        "dlon"  := "dlat" * sin("angle") / sqrt(
   1.450 -          ("geosearch_p"."c2" * "lat" + "geosearch_p"."c1") * "lat" +
   1.451 -          "geosearch_p"."c0" );
   1.452 -        "coords"[2*"step"+1] := "lat";
   1.453 -        "coords"[2*"step"+2] := "geosearch_p"."lon" + "dlon";
   1.454 -        EXIT WHEN "step" >= "halfsteps";
   1.455 -        IF "step" > 0 THEN
   1.456 -          "coords"[2*("steps_p"-"step")+1] := "lat";
   1.457 -          "coords"[2*("steps_p"-"step")+2] := "geosearch_p"."lon" - "dlon";
   1.458 -        END IF;
   1.459 -        "step" := "step" + 1;
   1.460 -      END LOOP;
   1.461 -      RETURN array_to_string("coords", ',')::POLYGON;
   1.462 -    END;
   1.463 -  $$;
   1.464 -
   1.465 -COMMENT ON FUNCTION "polysearch"
   1.466 -  ("geosearch", INTEGER)
   1.467 -  IS 'Returns a polygon approximating the search circle represented by a given "geosearch" structure; The optional second parameter denotes the number of vertices (defaults to 24)';
   1.468 -
   1.469 -
   1.470 -CREATE FUNCTION "polysearch"
   1.471 -  ( "latitude_p"  FLOAT,
   1.472 -    "longitude_p" FLOAT,
   1.473 -    "radius_p"    FLOAT,
   1.474 -    "steps_p"     INTEGER = 24 )
   1.475 -  RETURNS POLYGON
   1.476 -  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
   1.477 -    DECLARE
   1.478 -      "geosearch_v" "geosearch";
   1.479 -    BEGIN
   1.480 -      "geosearch_v" := "geosearch"("latitude_p", "longitude_p");
   1.481 -      "geosearch_v"."dist" := "radius_p";
   1.482 -      RETURN "polysearch"("geosearch_v", "steps_p");
   1.483 -    END;
   1.484 -  $$;
   1.485 -
   1.486 -COMMENT ON FUNCTION "polysearch"
   1.487 -  ("geosearch", INTEGER)
   1.488 -  IS 'Returns a polygon approximating a search circle around a point given as latitude and longitude in degrees as well as the radius in meters; The optional second parameter denotes the number of vertices (defaults to 24)';
   1.489 -
   1.490 -
   1.491 -/* Usage of spatial indexing for other basic geometric objects:
   1.492 -
   1.493 --- create table containing geographical coordinates in degrees:
   1.494 -CREATE TABLE "t2" (
   1.495 -        "id"            SERIAL4         PRIMARY KEY,
   1.496 -        "geometry"      PATH );
   1.497 -
   1.498 --- create a GiST (not SP-GiST!) index (with default poly_ops operator class):
   1.499 -CREATE INDEX "t2_geoindex" ON "t2" USING gist (("polyindex"("geometry")));
   1.500 -
   1.501 --- return results within 10,000 meters of Brandenburg Gate:
   1.502 --- NOTE: use && operator for overlapping, use <@ operator for containment
   1.503 -SELECT "t2".*
   1.504 -  FROM "t2"
   1.505 -  CROSS JOIN "polysearch"(52.5162746, 13.377704, 10000) AS "poly1"
   1.506 -  CROSS JOIN LATERAL "geoshift180"("poly1") AS "poly2"
   1.507 -  WHERE (
   1.508 -    "polyindex"("geometry") && "poly1" OR
   1.509 -    "polyindex"("geometry") && "poly2" );
   1.510 -
   1.511 -*/
   1.512 -
   1.513 -
   1.514 -CREATE FUNCTION "scattered_polygon_contains_point"
   1.515 -  ( "scattered_polygon_p" POLYGON[],
   1.516 -    "point_p"             POINT )
   1.517 -  RETURNS BOOLEAN
   1.518 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.519 -    SELECT count(1) % 2 = 1
   1.520 -      FROM unnest("scattered_polygon_p") AS "entry"
   1.521 -      WHERE "entry" @> "point_p"
   1.522 -  $$;
   1.523 -
   1.524 -COMMENT ON FUNCTION "scattered_polygon_contains_point"
   1.525 -  ( POLYGON[], POINT )
   1.526 -  IS 'Takes a scattered (or hollow) polygon represented as an array of polygons and, as a second argument, a point; returns TRUE if the number of polygons in which the point is contained is odd';
   1.527 -
   1.528 -
   1.529 -CREATE FUNCTION "scattered_polygon_contains_point"
   1.530 -  ( "scattered_polygon_p" POLYGON[],
   1.531 -    "point_x_p"           FLOAT,
   1.532 -    "point_y_p"           FLOAT )
   1.533 -  RETURNS BOOLEAN
   1.534 -  LANGUAGE sql IMMUTABLE STRICT AS $$
   1.535 -    SELECT "scattered_polygon_contains_point"(
   1.536 -      "scattered_polygon_p",
   1.537 -      point("point_x_p", "point_y_p")
   1.538 -    )
   1.539 -  $$;
   1.540 -
   1.541 -COMMENT ON FUNCTION "scattered_polygon_contains_point"
   1.542 -  ( POLYGON[], FLOAT, FLOAT )
   1.543 -  IS 'Takes a scattered (or hollow) polygon represented as an array of polygons and, as second and third arguments, two coordinates representing a point; returns TRUE if the number of polygons in which the point is contained is odd';
   1.544 -
   1.545 -
   1.546 -CREATE FUNCTION "scattered_polygon_bound_box"
   1.547 -  ( "scattered_polygon_p" POLYGON[] )
   1.548 -  RETURNS BOX
   1.549 -  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
   1.550 -    DECLARE
   1.551 -      "first_v"     BOOLEAN;
   1.552 -      "bound_box_v" BOX;
   1.553 -      "polygon_v"   POLYGON;
   1.554 -    BEGIN
   1.555 -      "first_v" := TRUE;
   1.556 -      FOR "polygon_v" IN SELECT * FROM unnest("scattered_polygon_p") LOOP
   1.557 -        IF "first_v" THEN
   1.558 -          "bound_box_v" := box("polygon_v");
   1.559 -          "first_v" := FALSE;
   1.560 -        ELSE
   1.561 -          "bound_box_v" := bound_box("bound_box_v", box("polygon_v"));
   1.562 -        END IF;
   1.563 -      END LOOP;
   1.564 -      RETURN "bound_box_v";
   1.565 -    END;
   1.566 -  $$;
   1.567 -
   1.568 -COMMENT ON FUNCTION "scattered_polygon_bound_box"
   1.569 -  ( POLYGON[] )
   1.570 -  IS 'Takes a scattered (or hollow) polygon represented as an array of polygons and returns a bounding box';
   1.571 -
   1.572 -
   1.573 -/* Usage of spatial indexing for scattered (or hollow) polygons:
   1.574 -
   1.575 --- create table containing geographical coordinates in degrees:
   1.576 -CREATE TABLE "t3" (
   1.577 -        "id"            SERIAL4         PRIMARY KEY,
   1.578 -        "region"        POLYGON[] );
   1.579 -
   1.580 --- create a GiST (not SP-GiST!) index (with default box_ops operator class):
   1.581 -CREATE INDEX "t3_geoindex" ON "t3" USING gist (("scattered_polygon_bound_box"("region")));
   1.582 -
   1.583 --- return rows containing Brandenburg Gate's location:
   1.584 -SELECT "t3".*
   1.585 -  FROM "t3"
   1.586 -  CROSS JOIN point(52.5162746, 13.377704) AS "point1"
   1.587 -  CROSS JOIN LATERAL "geoshift180"("point1") AS "point2"
   1.588 -  WHERE (
   1.589 -    "scattered_polygon_contains_point"("region", "point1") OR
   1.590 -    "scattered_polygon_contains_point"("region", "point2") );
   1.591 -
   1.592 -*/
   1.593 -
   1.594 -
   1.595 -
   1.596  -------------------------
   1.597  -- Tables and indicies --
   1.598  -------------------------
   1.599 @@ -717,8 +139,7 @@
   1.600          "external_posts"        TEXT,
   1.601          "formatting_engine"     TEXT,
   1.602          "statement"             TEXT,
   1.603 -        "latitude"              NUMERIC,
   1.604 -        "longitude"             NUMERIC,
   1.605 +        "location"              EPOINT,
   1.606          "text_search_data"      TSVECTOR,
   1.607          CONSTRAINT "active_requires_activated_and_last_activity"
   1.608            CHECK ("active" = FALSE OR ("activated" NOTNULL AND "last_activity" NOTNULL)),
   1.609 @@ -731,12 +152,10 @@
   1.610          CONSTRAINT "notification_dow_requires_notification_hour"
   1.611            CHECK ("notification_dow" ISNULL OR "notification_hour" NOTNULL),
   1.612          CONSTRAINT "name_not_null_if_activated"
   1.613 -          CHECK ("activated" ISNULL OR "name" NOTNULL),
   1.614 -        CONSTRAINT "latitude_and_lontitude_both_null_or_not_null"
   1.615 -          CHECK (("latitude" NOTNULL) = ("longitude" NOTNULL)) );
   1.616 +          CHECK ("activated" ISNULL OR "name" NOTNULL) );
   1.617  CREATE INDEX "member_authority_login_idx" ON "member" ("authority_login");
   1.618  CREATE INDEX "member_active_idx" ON "member" ("active");
   1.619 -CREATE INDEX "member_geolocation_idx" ON "member" USING spgist (("geoindex"("latitude", "longitude")));
   1.620 +CREATE INDEX "member_location_idx" ON "member" USING gist ("location");
   1.621  CREATE INDEX "member_text_search_data_idx" ON "member" USING gin ("text_search_data");
   1.622  CREATE TRIGGER "update_text_search_data"
   1.623    BEFORE INSERT OR UPDATE ON "member"
   1.624 @@ -789,8 +208,7 @@
   1.625  COMMENT ON COLUMN "member"."external_posts"       IS 'Posts (offices) outside the organization';
   1.626  COMMENT ON COLUMN "member"."formatting_engine"    IS 'Allows different formatting engines (i.e. wiki formats) to be used for "member"."statement"';
   1.627  COMMENT ON COLUMN "member"."statement"            IS 'Freely chosen text of the member for his/her profile';
   1.628 -COMMENT ON COLUMN "member"."latitude"             IS 'Latitude (in degrees) of member''s location';
   1.629 -COMMENT ON COLUMN "member"."longitude"            IS 'Longitude (in degrees) of member''s location';
   1.630 +COMMENT ON COLUMN "member"."location"             IS 'Geographic location on earth';
   1.631  
   1.632  
   1.633  CREATE TABLE "member_history" (
   1.634 @@ -1034,12 +452,12 @@
   1.635          "description"           TEXT            NOT NULL DEFAULT '',
   1.636          "external_reference"    TEXT,
   1.637          "member_count"          INT4,
   1.638 -        "region"                POLYGON[],
   1.639 +        "region"                ECLUSTER,
   1.640          "text_search_data"      TSVECTOR );
   1.641  CREATE INDEX "unit_root_idx" ON "unit" ("id") WHERE "parent_id" ISNULL;
   1.642  CREATE INDEX "unit_parent_id_idx" ON "unit" ("parent_id");
   1.643  CREATE INDEX "unit_active_idx" ON "unit" ("active");
   1.644 -CREATE INDEX "unit_geolocation_idx" ON "unit" USING gist (("scattered_polygon_bound_box"("region")));
   1.645 +CREATE INDEX "unit_region_idx" ON "unit" USING gist ("region");
   1.646  CREATE INDEX "unit_text_search_data_idx" ON "unit" USING gin ("text_search_data");
   1.647  CREATE TRIGGER "update_text_search_data"
   1.648    BEFORE INSERT OR UPDATE ON "unit"
   1.649 @@ -1083,11 +501,11 @@
   1.650          "name"                  TEXT            NOT NULL,
   1.651          "description"           TEXT            NOT NULL DEFAULT '',
   1.652          "external_reference"    TEXT,
   1.653 -        "region"                POLYGON[],
   1.654 +        "region"                ECLUSTER,
   1.655          "text_search_data"      TSVECTOR );
   1.656  CREATE INDEX "area_unit_id_idx" ON "area" ("unit_id");
   1.657  CREATE INDEX "area_active_idx" ON "area" ("active");
   1.658 -CREATE INDEX "area_geolocation_idx" ON "area" USING gist (("scattered_polygon_bound_box"("region")));
   1.659 +CREATE INDEX "area_region_idx" ON "area" USING gist ("region");
   1.660  CREATE INDEX "area_text_search_data_idx" ON "area" USING gin ("text_search_data");
   1.661  CREATE TRIGGER "update_text_search_data"
   1.662    BEFORE INSERT OR UPDATE ON "area"
   1.663 @@ -1318,10 +736,8 @@
   1.664          "revoked"               TIMESTAMPTZ,
   1.665          "revoked_by_member_id"  INT4            REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
   1.666          "suggested_initiative_id" INT4          REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.667 -        "latitude1"             NUMERIC,
   1.668 -        "longitude1"            NUMERIC,
   1.669 -        "latitude2"             NUMERIC,
   1.670 -        "longitude2"            NUMERIC,
   1.671 +        "location1"             EPOINT,
   1.672 +        "location2"             EPOINT,
   1.673          "external_reference"    TEXT,
   1.674          "admitted"              BOOLEAN,
   1.675          "supporter_count"                    INT4,
   1.676 @@ -1349,12 +765,8 @@
   1.677            CHECK (("revoked" NOTNULL) = ("revoked_by_member_id" NOTNULL)),
   1.678          CONSTRAINT "non_revoked_initiatives_cant_suggest_other"
   1.679            CHECK ("revoked" NOTNULL OR "suggested_initiative_id" ISNULL),
   1.680 -        CONSTRAINT "latitude1_and_lontitude1_both_null_or_not_null"
   1.681 -          CHECK (("latitude1" NOTNULL) = ("longitude1" NOTNULL)),
   1.682 -        CONSTRAINT "latitude2_and_longitude2_both_null_or_not_null"
   1.683 -          CHECK (("latitude2" NOTNULL) = ("longitude2" NOTNULL)),
   1.684 -        CONSTRAINT "latutude2_requires_latitude1"
   1.685 -          CHECK ("latitude2" ISNULL OR "latitude1" NOTNULL),
   1.686 +        CONSTRAINT "location2_requires_location1"
   1.687 +          CHECK ("location2" ISNULL OR "location1" NOTNULL),
   1.688          CONSTRAINT "revoked_initiatives_cant_be_admitted"
   1.689            CHECK ("revoked" ISNULL OR "admitted" ISNULL),
   1.690          CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results" CHECK (
   1.691 @@ -1376,8 +788,8 @@
   1.692          CONSTRAINT "unique_rank_per_issue" UNIQUE ("issue_id", "rank") );
   1.693  CREATE INDEX "initiative_created_idx" ON "initiative" ("created");
   1.694  CREATE INDEX "initiative_revoked_idx" ON "initiative" ("revoked");
   1.695 -CREATE INDEX "initiative_geolocation1_idx" ON "initiative" USING spgist (("geoindex"("latitude1", "longitude1")));
   1.696 -CREATE INDEX "initiative_geolocation2_idx" ON "initiative" USING spgist (("geoindex"("latitude2", "longitude2")));
   1.697 +CREATE INDEX "initiative_location1_idx" ON "initiative" USING gist ("location1");
   1.698 +CREATE INDEX "initiative_location2_idx" ON "initiative" USING gist ("location2");
   1.699  CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
   1.700  CREATE INDEX "initiative_draft_text_search_data_idx" ON "initiative" USING gin ("draft_text_search_data");
   1.701  CREATE TRIGGER "update_text_search_data"
   1.702 @@ -1390,10 +802,8 @@
   1.703  COMMENT ON COLUMN "initiative"."polling"                IS 'Initiative does not need to pass the initiative quorum (see "policy"."polling")';
   1.704  COMMENT ON COLUMN "initiative"."revoked"                IS 'Point in time, when one initiator decided to revoke the initiative';
   1.705  COMMENT ON COLUMN "initiative"."revoked_by_member_id"   IS 'Member, who decided to revoke the initiative';
   1.706 -COMMENT ON COLUMN "initiative"."latitude1"              IS 'Latitude (in degrees) of initiative (automatically copied from most recent draft)';
   1.707 -COMMENT ON COLUMN "initiative"."longitude1"             IS 'Longitude (in degrees) of initiative (automatically copied from most recent draft)';
   1.708 -COMMENT ON COLUMN "initiative"."latitude2"              IS 'Latitude (in degrees) of initiative''s second marker (automatically copied from most recent draft)';
   1.709 -COMMENT ON COLUMN "initiative"."longitude2"             IS 'Longitude (in degrees) of initiative''s second marker (automatically copied from most recent draft)';
   1.710 +COMMENT ON COLUMN "initiative"."location1"              IS 'Geographic location of initiative (automatically copied from most recent draft)';
   1.711 +COMMENT ON COLUMN "initiative"."location2"              IS 'Geographic location of initiative''s second marker (automatically copied from most recent draft)';
   1.712  COMMENT ON COLUMN "initiative"."external_reference"     IS 'Opaque data field to store an external reference';
   1.713  COMMENT ON COLUMN "initiative"."admitted"               IS 'TRUE, if initiative reaches the "initiative_quorum" when freezing the issue';
   1.714  COMMENT ON COLUMN "initiative"."supporter_count"                    IS 'Calculated from table "direct_supporter_snapshot"';
   1.715 @@ -1462,22 +872,16 @@
   1.716          "author_id"             INT4            NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
   1.717          "formatting_engine"     TEXT,
   1.718          "content"               TEXT            NOT NULL,
   1.719 -        "latitude1"             NUMERIC,
   1.720 -        "longitude1"            NUMERIC,
   1.721 -        "latitude2"             NUMERIC,
   1.722 -        "longitude2"            NUMERIC,
   1.723 +        "location1"             EPOINT,
   1.724 +        "location2"             EPOINT,
   1.725          "external_reference"    TEXT,
   1.726          "text_search_data"      TSVECTOR,
   1.727 -        CONSTRAINT "latitude1_and_lontitude1_both_null_or_not_null"
   1.728 -          CHECK (("latitude1" NOTNULL) = ("longitude1" NOTNULL)),
   1.729 -        CONSTRAINT "latitude2_and_longitude2_both_null_or_not_null"
   1.730 -          CHECK (("latitude2" NOTNULL) = ("longitude2" NOTNULL)),
   1.731 -        CONSTRAINT "latutude2_requires_latitude1"
   1.732 -          CHECK ("latitude2" ISNULL OR "latitude1" NOTNULL) );
   1.733 +        CONSTRAINT "location2_requires_location1"
   1.734 +          CHECK ("location2" ISNULL OR "location1" NOTNULL) );
   1.735  CREATE INDEX "draft_created_idx" ON "draft" ("created");
   1.736  CREATE INDEX "draft_author_id_created_idx" ON "draft" ("author_id", "created");
   1.737 -CREATE INDEX "draft_geolocation1_idx" ON "draft" USING spgist (("geoindex"("latitude1", "longitude1")));
   1.738 -CREATE INDEX "draft_geolocation2_idx" ON "draft" USING spgist (("geoindex"("latitude2", "longitude2")));
   1.739 +CREATE INDEX "draft_location1_idx" ON "draft" USING gist ("location1");
   1.740 +CREATE INDEX "draft_location2_idx" ON "draft" USING gist ("location2");
   1.741  CREATE INDEX "draft_text_search_data_idx" ON "draft" USING gin ("text_search_data");
   1.742  CREATE TRIGGER "update_text_search_data"
   1.743    BEFORE INSERT OR UPDATE ON "draft"
   1.744 @@ -1488,10 +892,8 @@
   1.745  
   1.746  COMMENT ON COLUMN "draft"."formatting_engine"  IS 'Allows different formatting engines (i.e. wiki formats) to be used';
   1.747  COMMENT ON COLUMN "draft"."content"            IS 'Text of the draft in a format depending on the field "formatting_engine"';
   1.748 -COMMENT ON COLUMN "draft"."latitude1"          IS 'Latitude (in degrees) of initiative (automatically copied to "initiative" table if draft is most recent)';
   1.749 -COMMENT ON COLUMN "draft"."longitude1"         IS 'Longitude (in degrees) of initiative (automatically copied to "initiative" table if draft is most recent)';
   1.750 -COMMENT ON COLUMN "draft"."latitude2"          IS 'Latitude (in degrees) of initiative''s second marker (automatically copied to "initiative" table if draft is most recent)';
   1.751 -COMMENT ON COLUMN "draft"."longitude2"         IS 'Longitude (in degrees) of initiative''s second marker (automatically copied to "initiative" table if draft is most recent)';
   1.752 +COMMENT ON COLUMN "draft"."location1"          IS 'Geographic location of initiative (automatically copied to "initiative" table if draft is most recent)';
   1.753 +COMMENT ON COLUMN "draft"."location2"          IS 'Geographic location of initiative''s second marker (automatically copied to "initiative" table if draft is most recent)';
   1.754  COMMENT ON COLUMN "draft"."external_reference" IS 'Opaque data field to store an external reference';
   1.755  
   1.756  
   1.757 @@ -1515,10 +917,8 @@
   1.758          "name"                  TEXT            NOT NULL,
   1.759          "formatting_engine"     TEXT,
   1.760          "content"               TEXT            NOT NULL DEFAULT '',
   1.761 -        "latitude1"             NUMERIC,
   1.762 -        "longitude1"            NUMERIC,
   1.763 -        "latitude2"             NUMERIC,
   1.764 -        "longitude2"            NUMERIC,
   1.765 +        "location1"             EPOINT,
   1.766 +        "location2"             EPOINT,
   1.767          "external_reference"    TEXT,
   1.768          "text_search_data"      TSVECTOR,
   1.769          "minus2_unfulfilled_count" INT4,
   1.770 @@ -1530,16 +930,12 @@
   1.771          "plus2_unfulfilled_count"  INT4,
   1.772          "plus2_fulfilled_count"    INT4,
   1.773          "proportional_order"    INT4,
   1.774 -        CONSTRAINT "latitude1_and_lontitude1_both_null_or_not_null"
   1.775 -          CHECK (("latitude1" NOTNULL) = ("longitude1" NOTNULL)),
   1.776 -        CONSTRAINT "latitude2_and_longitude2_both_null_or_not_null"
   1.777 -          CHECK (("latitude2" NOTNULL) = ("longitude2" NOTNULL)),
   1.778 -        CONSTRAINT "latutude2_requires_latitude1"
   1.779 -          CHECK ("latitude2" ISNULL OR "latitude1" NOTNULL) );
   1.780 +        CONSTRAINT "location2_requires_location1"
   1.781 +          CHECK ("location2" ISNULL OR "location1" NOTNULL) );
   1.782  CREATE INDEX "suggestion_created_idx" ON "suggestion" ("created");
   1.783  CREATE INDEX "suggestion_author_id_created_idx" ON "suggestion" ("author_id", "created");
   1.784 -CREATE INDEX "suggestion_geolocation1_idx" ON "suggestion" USING spgist (("geoindex"("latitude1", "longitude1")));
   1.785 -CREATE INDEX "suggestion_geolocation2_idx" ON "suggestion" USING spgist (("geoindex"("latitude2", "longitude2")));
   1.786 +CREATE INDEX "suggestion_location1_idx" ON "suggestion" USING gist ("location1");
   1.787 +CREATE INDEX "suggestion_location2_idx" ON "suggestion" USING gist ("location2");
   1.788  CREATE INDEX "suggestion_text_search_data_idx" ON "suggestion" USING gin ("text_search_data");
   1.789  CREATE TRIGGER "update_text_search_data"
   1.790    BEFORE INSERT OR UPDATE ON "suggestion"
   1.791 @@ -1550,10 +946,8 @@
   1.792  COMMENT ON TABLE "suggestion" IS 'Suggestions to initiators, to change the current draft; must not be deleted explicitly, as they vanish automatically if the last opinion is deleted';
   1.793  
   1.794  COMMENT ON COLUMN "suggestion"."draft_id"                 IS 'Draft, which the author has seen when composing the suggestion; should always be set by a frontend, but defaults to current draft of the initiative (implemented by trigger "default_for_draft_id")';
   1.795 -COMMENT ON COLUMN "suggestion"."latitude1"                IS 'Latitude (in degrees) of suggestion';
   1.796 -COMMENT ON COLUMN "suggestion"."longitude1"               IS 'Longitude (in degrees) of suggestion';
   1.797 -COMMENT ON COLUMN "suggestion"."latitude2"                IS 'Latitude (in degrees) of suggestion''s second marker';
   1.798 -COMMENT ON COLUMN "suggestion"."longitude2"               IS 'Longitude (in degrees) of suggestion''s second marker';
   1.799 +COMMENT ON COLUMN "suggestion"."location1"                IS 'Geographic location of suggestion';
   1.800 +COMMENT ON COLUMN "suggestion"."location2"                IS 'Geographic location of suggestion''s second marker';
   1.801  COMMENT ON COLUMN "suggestion"."external_reference"       IS 'Opaque data field to store an external reference';
   1.802  COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
   1.803  COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count"   IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
   1.804 @@ -2552,10 +1946,8 @@
   1.805        PERFORM NULL FROM "initiative" WHERE "id" = "initiative_id_p"
   1.806          FOR UPDATE;
   1.807        UPDATE "initiative" SET
   1.808 -        "latitude1"  = "draft"."latitude1",
   1.809 -        "longitude1" = "draft"."longitude1",
   1.810 -        "latitude2"  = "draft"."latitude2",
   1.811 -        "longitude2" = "draft"."longitude2",
   1.812 +        "location1"  = "draft"."location1",
   1.813 +        "location2"  = "draft"."location2",
   1.814          "draft_text_search_data" = "draft"."text_search_data"
   1.815          FROM "current_draft" AS "draft"
   1.816          WHERE "initiative"."id" = "initiative_id_p"

Impressum / About Us