Determine If PostGIS Geometry Is a Circle
You could try calculating a compactness score for your geometries to see if they are a circle. Something like the Polsby-Popper test will calculate a ratio between 1 and 0, 1 being a perfect circle and any other geometric shape will have a smaller ratio.
4 * pi() * (area/(perimeter^2))
If you are working with perfect circles you can select anything with a ratio of 1.
Here is a blog post on calculating the compactness of congressional districts in PostGIS where they use a few compactness tests one of which is the Polsby-Popper.
https://www.azavea.com/blog/2016/07/11/measuring-district-compactness-postgis/
You can compute a compactness test in a query, but you really don't want to. Here's an example of a small (100k row) table:
DROP TABLE IF EXISTS example1
;
CREATE TABLE example1 (
idcol serial NOT NULL,
isCircle char(1) NULL,
geomcol geometry NULL,
CONSTRAINT example1_pk PRIMARY KEY (idcol),
CONSTRAINT enforce_srid CHECK (st_srid(geomcol) = 4326)
)
with (
OIDS=FALSE
);
INSERT INTO example1(isCircle,geomcol)
SELECT isCircle, (CASE WHEN isCircle = 'Y'
THEN ST_Buffer(point::geography,km*1000)::geometry
ELSE ST_Envelope(ST_Buffer(point::geography,km*1000)::geometry)::geometry
END) as geomcol
FROM (
SELECT ST_SetSRID(
ST_MakePoint(
(random()*360.0) - 180.0,
(acos(1.0 - 2.0 * random()) * 2.0 - pi()) * 90.0 / pi()),
4326) as point,
(CASE WHEN random() > 0.4 THEN 'Y' ELSE NULL END)::char(1) as isCircle,
floor(random()*50 + 50) as km
FROM generate_series(1, 100000) vtab
) vt ;
-- 100000 rows affected, 28.5 secs execution time.
CREATE INDEX example1_spx ON example1 USING GIST (geomcol);
CREATE INDEX example1_ix1 ON example1(isCircle);
-- Query time
SELECT count(*) FROM example1
WHERE isCircle = 'Y';
-- 60174 rows in 30ms
SELECT count(*) FROM example1
WHERE 4 * pi() * (ST_Area(geography(geomcol),true) / (ST_Perimeter(geography(geomcol),true) * ST_Perimeter(geography(geomcol),true))) > 0.99;
-- 60174 rows in 18300ms
Computing the isCircle property once per geometry is going to represent a significant savings vice computing it with each query, even if you aren't working in geodetic space. This isn't a hack, just good SQL practice.
Addendum: As noted in @JGH's comment below, you can reduce the cost of the geodetic area and perimeter calculation by creating a function and a covering index on that function.
It's also faster to use the pow
function to square the perimeter value (at least on my PG 9.5 instance), and using that also drops the index query to effectively identical (140ms down to 32ms)
CREATE FUNCTION compactness( geometry ) RETURNS double precision AS $$
BEGIN
RETURN 4.0 * pi() * (ST_Area( $1 ::geography,true) /
pow(ST_Perimeter( $1 ::geography,true),2.0));
END;
$$ LANGUAGE plpgsql
IMMUTABLE
RETURNS NULL ON NULL INPUT;
CREATE INDEX example1_cx1 on example1(compactness(geomcol));
-- 12100ms (due to pow() function)
SELECT count(*) FROM example1 where compactness(geomcol) > 0.99;
-- 32ms
Still, if you have the isCircle
value up-front, it's not a hack to use it (and if it gets corrupted, you can use the compactness function to recover).