Use St_ExteriorRing, or similar, to fill up 'holes' in Multipolygon geojson - sql

I have an application that reads geojson from an external API. The geojson could be either Polygon or Multipolygon depending on the day.
These are examples of the two
Multipolygon
{"type":"MultiPolygon","coordinates":[[[[-99.51346135949517,49.00303923244011],[-100.15,47.95],[-102.31,47.45],[-108.58,46.38],[-109.18,46.08],[-111.49,44.89],[-112.76,44.42],[-114.63,43.9],[-116.74,43.72],[-118.71000000000001,42.86],[-118.87,42.36],[-118.11,42.11],[-116.35,42.63],[-115.24,42.27],[-114.45,42.12],[-112.6,42.27],[-111.36,42.76],[-110.7,42.98],[-107.75,43.9],[-106.11,43.66],[-104.91,42.66],[-104.77,41.68],[-104.04,40.6],[-103.49,39.34],[-102.87,39.26],[-102.41,38.64],[-102.31,37.41],[-101.91,36.68],[-101.2,36.36],[-100,36.46],[-98.74,38.68],[-98.39,38.95],[-97.53,39.53],[-97.02,39.44],[-96.43,39.32],[-95.05,38.49],[-93.51,37.47],[-93.13,37.15],[-91.08,35.28],[-88.52,34.63],[-86.34,34.86],[-85.31,35.74],[-84.18,36.63],[-83.04,36.79],[-81.59,36.01],[-81.13,35.08],[-80.45,34.34],[-80.32,34.26],[-79.57,33.82],[-78.81808696186206,33.27775502057361],[-78.931,32.928],[-79.294,32.69],[-79.725,32.427],[-80.061,32.203],[-80.21,32.07],[-80.436,31.966],[-80.764,31.633],[-81.09,30.74],[-80.989,30.128],[-80.765,29.498],[-80.349,28.83],[-80.197,28.552],[-80.271,28.232],[-79.788,27.148],[-79.7,26.777],[-79.766,26.228],[-79.812,25.576],[-79.965,25.158],[-80.143,24.859],[-80.331,24.705],[-80.635,24.487],[-81.054,24.361],[-81.782,24.208],[-82.816,24.169],[-83.288,24.221],[-83.28,24.513],[-83.238,24.798],[-83.163,25.012],[-82.313,25.017],[-82.368,25.134],[-82.51,25.288],[-82.632,25.453],[-81.934,25.61],[-82.143,26.09],[-82.324,26.171],[-82.535,26.48],[-82.719,26.892],[-83.105,27.532],[-83.07,27.615],[-83.183,27.781],[-83.185,28.105],[-83.156,28.373],[-83.014,28.465],[-82.99,28.551],[-83.026,28.753],[-83.273,28.827],[-83.421,29.093],[-83.625,29.29],[-83.895,29.642],[-84.073,29.73],[-84.171,29.626],[-84.467,29.438],[-85.018,29.246],[-85.336,29.335],[-85.675,29.436],[-85.767,29.696],[-85.985,29.876],[-86.462,30.068],[-87.771,29.878],[-88.406,29.863],[-88.432,29.714],[-88.695,29.394],[-88.581,29.249],[-88.619,29.022],[-88.937,28.691],[-89.175,28.63],[-89.399,28.577],[-89.712,28.693],[-89.828,28.916],[-90.102,28.755],[-90.3616055222485,28.728920906440788],[-90.59,29.18],[-90.87,30.19],[-90.74,31.24],[-90.18,31.97],[-89.72,32.76],[-90.62,34.34],[-93.28,37.05],[-95,37.73],[-96.23,37.81],[-97.39,37.44],[-98.65,36.55],[-100.01,35.09],[-101.27,34.54],[-102.23,34.53],[-103.01,34.66],[-103.73,34.32],[-103.8,33.75],[-103.99,33.41],[-104.32,32.69],[-105.35,32.06],[-105.9701240038253,31.300129741791523],[-106.325,31.547],[-106.448,31.769],[-107.166,31.772],[-107.78,31.773],[-108.182,31.777],[-108.213,31.305],[-109.08,31.308],[-109.891,31.327],[-110.732,31.321],[-111.103,31.32],[-112.189,31.67],[-112.66904089087537,31.81540516722906],[-112.7,32.69],[-111.95,33.62],[-110.88,34.28],[-109.67,34.89],[-108.43,35.58],[-107.8,36.94],[-107.19,38.87],[-106.27,40.84],[-106.23,41.55],[-106.45,42.34],[-107.19,42.6],[-109.05,42.48],[-110.95,41.14],[-112.4,40.46],[-115.28999999999999,39.87],[-117.96000000000001,39.46],[-119.89,39.24],[-121.74,39.12],[-123.06,39.74],[-123.78,41.24],[-123.28999999999999,43.33],[-121.28999999999999,45.26],[-119.8,46.42],[-118.65,47.32],[-119.25,48],[-120.4,48.34],[-121.84015239078087,48.98691337339889],[-121.659,48.986],[-120.472,48.999],[-119.253,48.999],[-117.737,49.015],[-116.09,48.994],[-114.493,48.985],[-113.116,49.003],[-111.3,48.99],[-109.984,48.991],[-109.64,48.997],[-108.993,48.996],[-107.31,48.993],[-105.605,49.009],[-103.573,48.991],[-101.079,48.988],[-99.687,49.004],[-99.51346135949517,49.00303923244011]],[[-84.43,34.91],[-86.05,34.1],[-86.37,33],[-85.19,32.64],[-84.24,32.79],[-83.28,33.27],[-82.39,33.89],[-82.05,34.63],[-82.23,35.26],[-83.17,35.46],[-84.43,34.91]]],[[[-88.87125436792684,48.17251743575732],[-88.4,47.64],[-88.31,46.8],[-88.89,45.98],[-89.68,45.43],[-90.53,43.89],[-90.58,42.8],[-90.29,42.37],[-89.65,41.69],[-88.48,41.31],[-86.62,41.26],[-83.61,41.42],[-81.71,41.54],[-80.43,42.18],[-80.35384895547264,42.36541123884922],[-80.167,42.399],[-79.353,42.673],[-78.966,42.881],[-79.194,43.433],[-78.597,43.655],[-77.369,43.615],[-76.813,43.628],[-76.377,44.151],[-75.828,44.443],[-75.494,44.756],[-74.876,45.025],[-71.551,45.021],[-71.366,45.294],[-70.884,45.379],[-70.38,45.841],[-70.319,46.114],[-70.03,46.658],[-69.251,47.478],[-68.967,47.467],[-68.967,47.24],[-68.497,47.296],[-68.287,47.417],[-67.785,47.096],[-67.744,46.598],[-67.724,45.726],[-67.403,45.548],[-67.372,45.222],[-67.182,45.297],[-67.043,45.044],[-66.976,44.938],[-66.991,44.84],[-66.86,44.821],[-67.302,44.414],[-67.279,44.152],[-67.925,43.906],[-68.184,43.802],[-68.352,43.817],[-68.538,43.761],[-68.681,43.688],[-68.902,43.581],[-69.149,43.51],[-69.388,43.422],[-69.557,43.333],[-69.738,43.3],[-69.962,43.13],[-70.094,42.939],[-70.041,42.768],[-69.908,42.491],[-69.795,42.31],[-69.595,42.133],[-69.483,42.001],[-69.443,41.721],[-69.548,41.498],[-69.488,41.199],[-69.663,41.018],[-69.946,40.916],[-70.34,40.942],[-70.838,40.931],[-71.244,40.859],[-71.608,40.77],[-72.156,40.586],[-72.567,40.462],[-72.944,40.361],[-73.2,40.303],[-73.648,40.236],[-73.751,39.803],[-73.839,39.533],[-74.085,39.198],[-74.24431116799899,39.091621252336154],[-74.72,40.43],[-74.13,41.32],[-73.27,42.18],[-73.28,42.96],[-73.69,43.37],[-75.34,43.41],[-76.08,42.83],[-76.99,42.2],[-77.99,41.95],[-79.57,41.64],[-81.97,41],[-84.85,40.86],[-88.66,40.91],[-89.93,41.4],[-91.14,41.9],[-91.69,42.6],[-92.39,43.51],[-92.06,44.4],[-91.92,45.58],[-92.15,46.97],[-92.73,48.56],[-92.72981693138672,48.56141140114221],[-92.611,48.546],[-92.049,48.36],[-91.504,48.094],[-90.835,48.281],[-90.646,48.094],[-90.054,48.116],[-89.35,47.985],[-88.87125436792684,48.17251743575732]]]]}
Polygon:
{"type":"Polygon","coordinates":[[[-95.28501052631579,48.998],[-94.58,48.27],[-92.76,47.02],[-90.19,46.11],[-88.05,46.5],[-86.48,47.07],[-85.95569942586447,47.33464695646842],[-85.896,47.31],[-84.848,46.885],[-84.707,46.516],[-84.142,46.508],[-84.043,46.128],[-83.963,46.047],[-83.859,46.044],[-83.754,46.112],[-83.517,46.102],[-83.444,45.977],[-83.53,45.879],[-83.584,45.814],[-82.528,45.336],[-82.199,44.145],[-82.103,43.62],[-82.288,43.266],[-82.307,43.137],[-82.454,42.997],[-82.483,42.619],[-82.704,42.431],[-82.729,42.36],[-83.068,42.27],[-83.189,42.064],[-83.08,41.93],[-83.103,41.858],[-82.7,41.693],[-82.32,41.713],[-81.62,42.066],[-81.185,42.216],[-80.167,42.399],[-79.353,42.673],[-78.966,42.881],[-79.194,43.433],[-78.597,43.655],[-77.369,43.615],[-76.813,43.628],[-76.377,44.151],[-75.828,44.443],[-75.494,44.756],[-75.18869307056579,44.888892498410684],[-75.38,44.11],[-75.46,41.79],[-74.54,40.67],[-73.68141943423038,40.095508592021794],[-73.751,39.803],[-73.839,39.533],[-74.085,39.198],[-74.395,38.991],[-74.504,38.814],[-74.716,38.67],[-74.701,38.442],[-74.762,38.239],[-75.086,37.666],[-75.263,37.548],[-75.356,37.334],[-75.645,36.894],[-75.371,36.199],[-75.12,35.649],[-75.166,35.243],[-75.279,34.984],[-75.429,34.931],[-75.726,34.857],[-76.265,34.375],[-76.54,34.248],[-76.776,34.354],[-76.936,34.358],[-77.521,34.032],[-77.684,33.615],[-77.95,33.516],[-78.163,33.577],[-78.569,33.483],[-78.808,33.309],[-78.931,32.928],[-79.294,32.69],[-79.725,32.427],[-80.061,32.203],[-80.21,32.07],[-80.436,31.966],[-80.764,31.633],[-81.09,30.74],[-80.989,30.128],[-80.765,29.498],[-80.349,28.83],[-80.197,28.552],[-80.271,28.232],[-79.788,27.148],[-79.7,26.777],[-79.766,26.228],[-79.812,25.576],[-79.965,25.158],[-80.143,24.859],[-80.331,24.705],[-80.635,24.487],[-81.054,24.361],[-81.782,24.208],[-82.816,24.169],[-83.288,24.221],[-83.28,24.513],[-83.238,24.798],[-83.163,25.012],[-82.313,25.017],[-82.368,25.134],[-82.51,25.288],[-82.632,25.453],[-81.934,25.61],[-82.143,26.09],[-82.324,26.171],[-82.535,26.48],[-82.719,26.892],[-83.105,27.532],[-83.07,27.615],[-83.183,27.781],[-83.185,28.105],[-83.156,28.373],[-83.014,28.465],[-82.99,28.551],[-83.026,28.753],[-83.273,28.827],[-83.421,29.093],[-83.625,29.29],[-83.895,29.642],[-84.073,29.73],[-84.171,29.626],[-84.467,29.438],[-85.018,29.246],[-85.336,29.335],[-85.675,29.436],[-85.767,29.696],[-85.985,29.876],[-86.462,30.068],[-87.771,29.878],[-88.406,29.863],[-88.432,29.714],[-88.695,29.394],[-88.581,29.249],[-88.619,29.022],[-88.937,28.691],[-89.175,28.63],[-89.399,28.577],[-89.712,28.693],[-89.828,28.916],[-90.102,28.755],[-90.54,28.711],[-90.745,28.711],[-91.083,28.732],[-91.315,28.893],[-91.556,28.983],[-91.655,29.121],[-91.742,29.058],[-92.079,29.114],[-92.164,29.181],[-92.629,29.233],[-93.243,29.441],[-93.588,29.43],[-93.777,29.35],[-93.86371715145437,29.3490574222668],[-94.43,30.11],[-95.48,32.28],[-95.9,34.79],[-97.03,36.61],[-98.25,37.5],[-99.48,37.64],[-100.37,37.51],[-100.95,36.99],[-101.57,36.09],[-102.05,35.51],[-103.01,35.26],[-103.97,35.42],[-104.89,36.02],[-105.46,36.22],[-106.3,35.84],[-104.73,33.68],[-104.99,32.67],[-106.17,32.34],[-107.24,32.43],[-107.23,31.95],[-107.16324568439407,31.771988491717522],[-107.166,31.772],[-107.78,31.773],[-108.182,31.777],[-108.213,31.305],[-109.08,31.308],[-109.891,31.327],[-110.732,31.321],[-111.103,31.32],[-111.5068844073521,31.450165324653078],[-111.32,31.76],[-110.88,32.62],[-111.06,34.11],[-110.37,35.35],[-109.05,36.35],[-108.06,37.78],[-107.21,39.63],[-105.82,41.42],[-105.1,41.94],[-105.06,42.36],[-105.26,42.78],[-105.62,43.15],[-106.56,43.87],[-107.39,44.07],[-109.01,43.9],[-111.36,43.17],[-113.32,43.43],[-114.49,43.16],[-115.82,42.89],[-116.23,42.73],[-118.37,43.02],[-120.51,43.78],[-121.1,44.75],[-119.58,46.35],[-119.09,47.98],[-119.44802702702702,48.999],[-119.253,48.999],[-117.737,49.015],[-116.09,48.994],[-114.493,48.985],[-113.116,49.003],[-111.3,48.99],[-109.984,48.991],[-109.64,48.997],[-108.993,48.996],[-107.31,48.993],[-105.605,49.009],[-103.573,48.991],[-101.079,48.988],[-99.687,49.004],[-98.242,48.996],[-96.798,48.991],[-95.874,48.998],[-95.28501052631579,48.998]],[[-90.2,32.05],[-88.72,30.79],[-88.05,30.65],[-87.21,30.8],[-86.45,31.65],[-85.88,32.11],[-85.02,32.55],[-83.71,32.58],[-82.55,33.06],[-81.69,33.45],[-81.33,34.29],[-80.13,35.88],[-79.91,37.41],[-79.69,38.28],[-80.79,38.96],[-83.11,38.16],[-85.63,38.75],[-88.01,38.14],[-90.04,37.53],[-91.48,36.43],[-92.4,35.03],[-92.58,34.06],[-91.41,32.64],[-90.2,32.05]],[[-109.65,45.06],[-100.16,42.48],[-99.01,41.67],[-97.4,38.75],[-95.53,35.91],[-94.12,35.24],[-93.06,35.66],[-90.26,38.1],[-90.85,40.46],[-92.65,43.69],[-94.58,44.37],[-98.8,44.91],[-102.84,46.18],[-110.55,46.83],[-111.85,46.61],[-112.49,46.05],[-111.89,45.39],[-109.65,45.06]]]}
The polygons almost always have a 'hole' in them, so I use:
ST_SetSRID(St_ExteriorRing(ST_GeomFromGeoJSON(mygeojson))
This works fine for Polygon but not for Multipolygon. I can understand why, as it may be a difficult task to "fill up the holes" of multiple geometries.
Is there a way to solve this so that multipolygons also can use ST_ExteriorRing (or the likes of it)? Conditional solutions are fine, I can find out which are polygons and multipolygons so the solution only needs to be for multipolygon.

One option is to ST_Dump the polygon, apply ST_ExteriorRing, and finally - if necessary - recreate the MultiPolygon with ST_Collect, e.g.
SELECT
ST_Collect(ST_MakePolygon(ST_ExteriorRing(geom)))
FROM (SELECT gid,(ST_Dump(mygeojson::geometry)).* FROM mytable) j
GROUP BY gid;
The query assumes each geometry has an identifier. In this case called gid. In case the geometries have no identifier, you might wanna create one with the window function ROW_NUMBER():
SELECT
ST_Collect(ST_MakePolygon(ST_ExteriorRing(geom)))
FROM (
SELECT
ROW_NUMBER() OVER w AS gid,
(ST_Dump(mygeojson::geometry)).*
FROM mytable
WINDOW w AS (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
) j
GROUP BY gid;
It gets much shorter if you can live with Polygons ;)
SELECT
ST_MakePolygon(ST_ExteriorRing((ST_Dump(mygeojson::geometry)).geom))
FROM mytable

Related

How to divide world into cells (grid)

How to divide the world into cells of almost equal size, such that each lat,lon can be mapped to a different cell?
I am pretty sure I've seen a library do this, labelling cells as S1, S2, etc..
Say we have 62.356279,-99.422395 , how to map it to a 2km*2km cell named "FR,23" ?
Thank you!
PostGIS 3.1+
PostGIS 3.1 introduces very easy to use grid generators, namely ST_SquareGrid and ST_HexagonGrid. An easy way to use these functions with data from a table is to use LATERAL to execute it, e.g. cells with 0.1° in size::
Sample Data
Consider the following polygon:
Creating a squared grid with cell size of 0.1° over a given geometry
SELECT grid.* FROM isle_of_man imn,
LATERAL ST_SquareGrid(0.1,imn.geom) grid;
If you only want the cells that interersect with the geometry, just call the function ST_Intersects in the WHERE clause:
SELECT grid.* FROM isle_of_man imn,
LATERAL ST_SquareGrid(0.1,imn.geom) grid
WHERE ST_Intersects(imn.geom,grid.geom);
The same principle applies to ST_HexagonGrid:
SELECT grid.* FROM isle_of_man imn,
LATERAL ST_HexagonGrid(0.1,imn.geom) grid;
SELECT grid.* FROM isle_of_man imn,
LATERAL ST_HexagonGrid(0.1,imn.geom) grid
WHERE ST_Intersects(imn.geom,grid.geom);
Older PostGIS versions
Inspired by this post I started writing a function to do just that - it still needs some tweaking but it'll certainly give you a direction look at. The following function creates a grid with cells of a given size covering the area of a given geometry:
CREATE OR REPLACE FUNCTION public.generate_grid(_size numeric,_geom geometry)
RETURNS TABLE(gid bigint, cell geometry) LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
_bbox box2d := ST_Extent(_geom);
_ncol int := ceil(abs(ST_Xmax(_bbox)-ST_Xmin(_bbox))/_size);
_nrow int := ceil(abs(ST_Ymax(_bbox)-ST_Ymin(_bbox))/_size);
_srid int DEFAULT 4326;
BEGIN
IF ST_SRID(_geom) <> 0 THEN
IF EXISTS (SELECT 1 FROM spatial_ref_sys crs
WHERE crs.srid = ST_SRID(_geom) AND NOT crs.proj4text LIKE '+proj=longlat%') THEN
RAISE EXCEPTION 'Only lon/lat spatial reference systems are supported in this function.';
ELSE
_srid := ST_SRID($2);
END IF;
END IF;
RETURN QUERY
SELECT ROW_NUMBER() OVER (), geom FROM (
SELECT
ST_SetSRID(
(ST_PixelAsPolygons(
ST_AddBand(
ST_MakeEmptyRaster(_ncol, _nrow, ST_XMin(_bbox), ST_YMax(_bbox),_size),
'1BB'::text, 1, 0),
1, false)).geom,_srid)) j(geom);
END;
$BODY$;
Note: This function relies on the extension PostGIS Raster.
SELECT cell FROM isle_of_man,
LATERAL generate_grid(0.1,geom);
... if you're only interested in cells that overlap your polygon, add a ST_Intersects to the query:
SELECT cell FROM isle_of_man,
LATERAL generate_grid(0.1,geom)
WHERE ST_Intersects(geom,cell)
Other alternatives
Mike's fishnet function does basically the same, but you'd need to manually provide the number of rows and columns, and the coordinate pair of the lower left corner:
SELECT ST_SetSRID(cells.geom,4326)
FROM ST_CreateFishnet(4, 6, 0.1, 0.1,-4.8411, 54.0364) AS cells;
You could use this makegrid_2d function to create a grid over an area using a polygon, e.g. a grid with cells of 5000 meters in size:
CREATE TABLE grid_isle_of_man AS
SELECT 'S'||ROW_NUMBER() OVER () AS grid_id, (g).geom
FROM (
SELECT ST_Dump(makegrid_2d(geom,5000))
FROM isle_of_man) j(g)
JOIN isle_of_man ON ST_Intersects((g).geom,geom);
The same logic applies for this hexagrid function. It creates a hexagon grid with fixed sized cells over a given BBOX. You can either manually provide the BBOX (function's second parameter) or extract it from a given polygon. For instance, to create a hexagrid that matches the polygon's extent and store it in a new table with the label you want - with cells of 0.1° in size:
CREATE TABLE hexgrid_isle_of_man AS
WITH j (hex_rec) AS (
SELECT generate_hexagons(0.1,ST_Extent(geom))
FROM isle_of_man
)
SELECT 'S'||ROW_NUMBER() OVER () AS grid_id,(hex_rec).hexagon FROM j
JOIN isle_of_man t ON ST_Intersects(t.geom,(hex_rec).hexagon);
Further reading:
Import World Shapefile
Waiting for PostGIS 3.1: Grid Generators, by Paul Ramsey
Jim's answer is excellent. There are use cases though, where you don't need the actual geometries. Where, as you've mentioned, all you need is coordinates in the same cell mapping to the same code. So instead of a costly point-in-polygon operation that takes O(n) for n polygons - without index, that is ;) - you call a function that simply evaluates a formula transforming a coordinate into a code in O(1). Very handy to aggregate data spatially fast.
Personally, I love Uber's H3 library for that sort of thing, but I'm sure S2 does something similar and does it well. There is a well-maintained PostgreSQL binding for H3 and a simple aggregation example would look something like this:
SELECT h3_geo_to_h3(geom_4326, 9) AS h3res09, SUM(pop_19) AS pop_19
FROM uk_postcode_population
GROUP BY 1;
Read: Sum up the postcode-level population of the UK for each resolution 9 hexagon.
You can still create the actual hexagon geometries when you need them (whole hex grids even). But in my experience, once you commit to the grid approach, you will only need polygons for visualisation in the very end.
I should note that you can't divide the world yourself using this library - Uber has already divided it for you. So if 2km squares are a hard requirement, this is not for you.
Installing the H3 extension is not as straightforward as CREATE EXTENSION postgis, but you don't need to be a command line warrior either. You will at the least have to install PGXN, most likely PostgreSQL's extension build library and the extension itself.
Someone asked for a point-in-polygon example in the comments. This is not exactly related to the question, but highlights how one would use H3.
Polygon layer prep:
CREATE TABLE poly_h3 AS (
SELECT id, h3_polyfill(geom_4326, 13) AS h3res13
FROM poly
);
CREATE INDEX ON poly_h3 (h3res13);
Point layer prep:
ALTER TABLE points ADD COLUMN h3res13 h3index;
UPDATE points
SET h3res13 = h3_geo_to_h3(geom_p_4326, 13);
CREATE INDEX ON points (h3res13);
Count points per polygon:
SELECT poly.id, COUNT(*) AS n
FROM poly_h3 AS poly
INNER JOIN points AS x
ON poly.h3res13 = x.h3res13
GROUP BY poly.id;

PostGIS query to Select all Polygons from Polygon table having an intersecting geometry with one or more point in passed list of GeoCoordinates

I have a polygons table in Postgres (using PostGIS extension) named polygon having two fields (geom, id).
If I want to query the id of the polygon which intersects with the geometry of input geo-coordinate then I can do it with the below query.
SELECT id, geom
FROM polygon
WHERE ST_Intersects(polygon.%s, ST_GeometryFromText(POINT(latitude logitude), 4326));
But now I have a use case where I am getting a lot of geo-coordinates in request(~60k), now I am breaking this into lists of 1k Geo-coordinate each and querying the id of the polygon intersecting with each geo-coordinate.
I am struggling with how to write. a query for this, or if anyone has a better solution for this please suggest.
Keep in mind that the right order of coordinate pairs is lon, lat, so creating a point with lat, lon in your query will return wrong results. Your query also misses the single quotes ' around the WKT coordinate, e.g. 'POINT(1 2)'.
That all being said, you could simply paginate your result sets using ORDER BY, LIMIT and OFFSET, e.g.
Getting the first 1000 records
SELECT id, geom FROM polygon
WHERE ST_Intersects(geom, 'SRID=4326;POINT(1 2)')
ORDER BY id
LIMIT 1000 OFFSET 0;
By changing the OFFSET value you are able to retrieve the next pages.
LIMIT 1000 OFFSET 1000;
and so on ..
LIMIT 1000 OFFSET 2000;
EDIT: One way to apply this query using multiple input points is to use a CTE / subquery (see comments), e.g.
WITH j(g) AS (
VALUES
('SRID=4326;POINT(1 1)'),
('SRID=4326;POINT(1 2)')
-- ... add as many geometries as you want
)
SELECT id, geom FROM polygon, j
WHERE ST_Intersects(geom, g::geometry)

How to get the intersection length of touching geometries with ST_Touches

I am trying to develop a query in Postgis, where it can solve this problem:
I have a geometry and I wanna know which of the polygons that touches it, there is the highest contact area of this geometry. After I recognize this polygon I will take its value in a specific column and put this value in the same column but in my geometry.
Someone know how can I do that? I am a new user in postgresql/postgis.
As pointed out by #JGH in the comments, the overlapping area will be zero if you use ST_Touches alone. What you can do is to filter out only the geometries that do touch your reference geometry and then use ST_Intersection to get the intersection area, so that you can finally calculate the length of the intersection with ST_Length.
Data Sample
The geometry values depicted above are inside the CTE:
WITH j (id,geom) AS (
VALUES
(1,'POLYGON((-4.64 54.19,-4.59 54.19,-4.59 54.17,-4.64 54.17,-4.64 54.19))'),
(2,'POLYGON((-4.59 54.19,-4.56 54.19,-4.56 54.17,-4.59 54.17,-4.59 54.19))'),
(3,'LINESTRING(-4.65 54.19,-4.57 54.21)'),
(4,'POLYGON((-4.66 54.21,-4.60 54.21,-4.60 54.20,-4.66 54.20,-4.66 54.21))'),
(5,'POINT(-4.57 54.20)')
)
SELECT
id,
ST_Length(
ST_Intersection(
geom,
'POLYGON((-4.62 54.22,-4.58 54.22,-4.58 54.19,
-4.62 54.19,-4.62 54.22))')) AS touch_length
FROM j
WHERE
ST_Touches(
geom,
'POLYGON((-4.62 54.22,-4.58 54.22,-4.58 54.19,
-4.62 54.19,-4.62 54.22))')
ORDER BY touch_length DESC
LIMIT 1;
id | touch_length
----+---------------------
1 | 0.03000000000000025
(1 Zeile)

Joining point value to polygon with With statements

I have loaded a function hex_grid, it works fine see here but now I have a hexagonal grid I would like to add up all the values from the overlapping centroid points from a polygon layer. I've used with statements to keep things a little tidy, both select statements work as expected, my problem occurs when joining, clearly I've done something wrong but I just can't see it, any help would be appreciated. Here's the code. The hex and points are both in the same crs.
with hex AS(
SELECT hex_grid(
1,
(ST_XMIN(sa.geom)),
(ST_YMIN(sa.geom)),
(ST_XMAX(sa.geom)),
(ST_YMAX(sa.geom)),
32635, 32635, 32635) as geom_h
FROM geodata.study_area sa),
points AS(
SELECT count(*) as count, ST_Centroid(su.geom) AS geom_p
FROM clas.survey_unit su
group by su.geom
)
SELECT hex.geom_h, points.count
FROM hex
JOIN points
ON ST_Contains(points.geom_p, hex.geom_h)
group by hex.geom_h, points.count;
ST_Contains should have the polygon first, which contains the points
ON ST_Contains(hex.geom_h,points.geom_p)

PostgreSQL - How to ignore gross errors when using AVG() and MAX()?

I have data in a table and I'd like to deliberately ignore some of the obviously incorrect data and take an average of the more plausible data.
Here's a simplified example of what I mean. Let's say I have a table that lists people and their height in cms.
I might use this to get the average height.....
SELECT AVG(height) FROM people;
That's fine if the data was all added correctly, but if they're (say) ten people in the database with correct heights, and one person who's height has been recorded as a billion centimeters tall then the AVG() won't return a sensible value - a classic example of GIGO (garbage in, garbage out)
Is there any way to adjust the above SQL function to ignore the outlying data points? - the data that is so different from all the rest it's got to be wrong?
I'm pretty sure the solution will involve one of the functions listed here but I'm having trouble finding some plain-english explanations of what they do and how they work.
UPDATE.......
My quoted example using height was selected for simplicity of explanation. Any proposed solution CAN'T simply filter between sensible values (i.e. height above 1.5m and below 2m) because for the actual data I'm using I don't know what the sensible values are! The solution needs to reject data that is massively different from the majority of the other data - so I guess that's where a knowledge of stats comes in handy.
Update 2) Sorry, going to have un-accept the answer I previously accepted (helpful though it was!). The standard deviation gives a value for the 'spread' of the data, but doesn't give any idea of where the outlying data is (i.e. stupidly tall people, or stupidly short people), so a clause like this...
WHERE height BETWEEN (SELECT a-2*sd FROM cte) AND (SELECT a+2*sd FROM cte);
Doesn't just remove the one stupidly tall person from one end of the range, it also removes all of the 'normal height' people from the other end of the range!
I can adjust the WHERE clause like this....
WHERE height BETWEEN (SELECT a-(sd/100) FROM cte) AND (SELECT a+(sd/100) FROM cte);
But I'm looking for a solution that doesn't require individual tweaking for each different set of data
You could use FILTER:
SELECT AVG(height) FILTER (WHERE height BETWEEN x AND y) AS avg_height
FROM people;
-- or `WHERE`:
SELECT AVG(height) AS avg_height
FROM people
WHERE height BETWEEN x AND y;
x and y are plausible values.
Alternatively you could filter out values that are outside range average() +/- 2*stddev()
WITH cte AS (
SELECT AVG(height) a, STDDEV(height) sd
FROM people
)
SELECT AVG(height)
FROM people
WHERE height BETWEEN (SELECT a-2*sd FROM cte) AND (SELECT a+2*sd FROM cte);
db<>fiddle demo