As christmas is coming closer, I want to share a small PostGIS-function that generates a star at a given point with some other specified characteristics. This function has not been built with a special use-case in mind, but just for fun. Yet, I hope someone finds this functionality interesting.
OK, so what is a star? When I am talking about a star I mean a polygon consisting of at least 3 spikes which are equally distributed along a circle. The following picture describes best what I want I understand as a “star”:
With Postgis I want to create stars that look like the the green polygon above. When one defines the center coordinate (the black spot in the center of the star), the number of spikes, and the length of both inner and outer radius (red lines), and an additional offset (in degrees, see the black arrow on top of the star), we are able to create all needed corners of the polygon (marke yellow above).
I will show two ways to compute everything we need, a readable, “processual” one and a more “geeky” one with only one line of SQL.
Let’s create an empty dummy function with all arguments we need, the logic will be inserted later:
CREATE OR REPLACE FUNCTION ST_StarAtPoint( IN point geometry, IN inner_radius numeric, IN outer_radius numeric, IN number_of_spikes integer, IN additional_rotation_degrees numeric ) RETURNS GEOMETRY AS $body$ DECLARE BEGIN -- For the moment we'll always return a simple point geometry RETURN 'POINT(0 0)'::geometry; END; $body$ LANGUAGE plpgsql;
We are now able to call the above function. This does not make too much sense right now, but to test for typos it should do the trick:
SELECT ST_AsText(ST_StarAtPoint('POINT(0 0)'::geometry, 1, 5, 8, 0));
Now let’s add code to check if we have been called with valid parameters:
IF (inner_radius > outer_radius) THEN RAISE EXCEPTION 'Inner radius must not be greater than outer radius.'; END IF; IF (number_of_spikes < 3) THEN RAISE EXCEPTION 'A star must have at least three spikes.'; END IF;
Also, we will calculate the radians of the given degrees
angle = radians(360/number_of_corners); additional_rotation_angle = radians(additional_rotation_degrees);
For all the variables we need to have a valid declaration. We’ll add four more variables to the declarations, the declartion section now looks like this:
-- the star geometry variable as WKT star_geometry_wkt text := ''; -- a loop counter i integer := 0; -- the angle defined by 360° / number of spikes angle numeric; -- an optional "offset" additional_rotation_angle numeric; -- the baseline we will rotate around for the inner points baseline_inner geometry; -- the baseline we will rotate around for the outerpoints baseline_outer geometry; -- the point we rotate around rotation_point geometry := 'POINT(0 0)'::geometry;
-- construction of the lines to rotate baseline_outer = ST_RotateZ( ST_MakeLine(rotation_point, ST_MakePoint( ST_X(rotation_point), ST_Y(rotation_point) + outer_radius) ), additional_rotation_angle); baseline_inner = ST_RotateZ( ST_MakeLine(rotation_point, ST_MakePoint( ST_X(rotation_point), ST_Y(rotation_point) + inner_radius) ), additional_rotation_angle + (angle/2));
Now baseline_outer should contain a linestring from Point(0 0) to Point(0 radius_outer) (think twelve o’ clock) and slightly rotated counter-clockwise. The same is true for baseline_inner, but this line is roitated half the general angle agin counter-clockwise, so that inner points always lie exactly in the middle between two outer points.
We are now iterating through all needed spikes and add WKT-strings (not so elegant, I know) two our star_geometry_wkt-variable:
WHILE (i < number_of_corners) LOOP -- add point to polygon for outer-spike -- note that we are adding the appropriate coordiantes from the input-geometry star_geometry_wkt = star_geometry_wkt || (ST_X(ST_EndPoint(ST_RotateZ(baseline_outer, angle * i))) + ST_X(point)) || ' ' || ST_Y(ST_EndPoint(ST_RotateZ(baseline_outer, angle * i))) + ST_Y(point) || ','; -- add point to polygon for inner-spike -- note that we are adding the appropriate coordiantes from the input-geometry star_geometry_wkt = star_geometry_wkt || (ST_X(ST_EndPoint(ST_RotateZ(baseline_inner, angle * i))) + ST_X(point)) || ' ' || ST_Y(ST_EndPoint(ST_RotateZ(baseline_inner, angle * i))) + ST_Y(point) || ','; -- increment counter i = i + 1; END LOOP;
Finally, let’s finish the WKT string and add the first point again so the linestring is closed.
star_geometry_wkt = star_geometry_wkt || (ST_X(ST_EndPoint(baseline_outer)) + ST_X(point)) || ' ' || (ST_Y(ST_EndPoint(baseline_outer)) + ST_Y(point)); star_geometry_wkt = 'POLYGON((' || star_geometry_wkt || '))'; RETURN star_geometry_wkt::geometry;
Here is the complete code of the star-function.
Of course it is possible to build the function with SQL only (Heres the source of ST_StarAtPoint with 1 SQL query only).
Now let’s see if any of the function works:
CREATE TABLE star_test AS SELECT ST_StarAtPoint(ST_MakePoint(random()*400, random()*400),1, 1 + ii, iii, i) AS the_geom FROM generate_series(1,50) i, generate_series(1,3) ii, generate_series(5,10) iii; -- add a primary key for QGIS ALTER TABLE star_test ADD COLUMN id SERIAL; ALTER TABLE star_test ADD PRIMARY KEY (id);
When viewed in QGIS, one should see a star-field similar to this one: