<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" version="2.0"><channel><title>Jacob Coblentz | CrunchyData Blog</title>
<atom:link href="https://www.crunchydata.com/blog/author/jacob-coblentz/rss.xml" rel="self" type="application/rss+xml" />
<link>https://www.crunchydata.com/blog/author/jacob-coblentz</link>
<image><url>https://www.crunchydata.com/build/_assets/jacob-coblentz.png-E2PHIJBH.webp</url>
<title>Jacob Coblentz | CrunchyData Blog</title>
<link>https://www.crunchydata.com/blog/author/jacob-coblentz</link>
<width>1536</width>
<height>1536</height></image>
<description>PostgreSQL experts from Crunchy Data share advice, performance tips, and guides on successfully running PostgreSQL and Kubernetes solutions</description>
<language>en-us</language>
<pubDate>Thu, 02 Mar 2023 11:00:00 EST</pubDate>
<dc:date>2023-03-02T16:00:00.000Z</dc:date>
<dc:language>en-us</dc:language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<item><title><![CDATA[ Geocoding with Web APIs in Postgres ]]></title>
<link>https://www.crunchydata.com/blog/geocoding-with-web-apis-in-postgres</link>
<description><![CDATA[ Jacob walks you through the steps on how to set up a geocoder with the US census geocoding API inside your database with a plpython function and triggers.  ]]></description>
<content:encoded><![CDATA[ <p>Geocoding is the process of taking addresses or location information and getting the coordinates for that location. Anytime you route a new location or look up a zip code, the back end is geocoding the location and then using the geocode inside other PostGIS functions to give you the routes, locations, and other data you asked for.<p>PostGIS comes equipped with an easy way to use the US Census data with the <a href=https://postgis.net/docs/postgis_installation.html#install_tiger_geocoder_extension>Tiger geocoder</a>. Using the Tiger geocoder requires downloading <strong>large amounts</strong> of census data and in space-limited databases, this may not be ideal. Using a geocoding web API service can be a space saving solution in these cases.<p>I am going to show you how to set up a really quick function using <code>plpython3u</code> to hit a web service geocoder every time that we get a new row in the database.<h3 id=installing-plpython3u><a href=#installing-plpython3u>Installing plpython3u</a></h3><p>The plpython3u extension comes with <a href=https://crunchybridge.com/start>Crunchy Bridge</a> or you can add it to your database. To get started run the following:<pre><code class=language-pgsql>CREATE EXTENSION  plpython3u;
</code></pre><h3 id=creating-a-function-to-geocode-addresses><a href=#creating-a-function-to-geocode-addresses>Creating a function to geocode addresses</a></h3><p>In this example, I'll use the <a href=https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.html>US census geocoding API</a> as our web service, and build a function to geocode addresses based on that.<p>The function puts together parameters to hit the census geocoding API and then parses the resulting object, and returns a geometry:<pre><code class=language-pgsql>CREATE OR REPLACE FUNCTION geocode(address text)
RETURNS geometry
AS $$
	import requests
	try:
		payload = {'address' : address , 'benchmark' : 2020, 'format' : 'json'}
		base_geocode = 'https://geocoding.geo.census.gov/geocoder/locations/onelineaddress'
		r = requests.get(base_geocode, params = payload)
		coords = r.json()['result']['addressMatches'][0]['coordinates']
		lon = coords['x']
		lat = coords['y']
		geom = f'SRID=4326;POINT({lon} {lat})'
	except Exception as e:
		plpy.notice(f'address failed: {address}')
		plpy.notice(f'error: {e.message}')
		geom = None
	return geom
$$
LANGUAGE 'plpython3u';
</code></pre><p>Using this function to geocode Crunchy Data's headquarters:<pre><code class=language-pgsql>SELECT ST_AsText(geocode('162 Seven Farms Drive Charleston, SC 29492'));
</code></pre><h3 id=deploying-this-function-for-new-data><a href=#deploying-this-function-for-new-data>Deploying this function for new data</a></h3><p>But what if we want to automatically run this every time an address is inserted into a table? Let's say we have a table with a field ID, an address, and a point that we want to auto-populate on inserts.<pre><code class=language-pgsql>CREATE TABLE addresses (
	fid SERIAL PRIMARY KEY,
	address VARCHAR,
	geom GEOMETRY(POINT, 4326)
);
</code></pre><p>We can make use of a Postgres trigger to add the geocode before every insert! Triggers are a very powerful way to leverage built in functions to automatically transform your data as it enters the database, and this particular case is a great demo for them!<pre><code class=language-pgsql>CREATE OR REPLACE FUNCTION add_geocode()
RETURNS trigger AS
$$
DECLARE
    loc geometry;
BEGIN
    loc := geocode(NEW.address);
    NEW.geom = loc;
    RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER update_geocode BEFORE INSERT ON addresses
    FOR EACH ROW EXECUTE FUNCTION add_geocode();
</code></pre><p>Now when running an insert, the value is automatically geocoded!<pre><code class=language-pgsql>INSERT INTO addresses(address) VALUES ('415 Mission St, San Francisco, CA 94105');

postgres=# SELECT fid, address, ST_AsText(geom) FROM addresses;
 fid |                 address                 |                        geom
-----+-----------------------------------------+----------------------------------------------------
   1 | 415 Mission St, San Francisco, CA 94105 | 0101000020E610000097CD0E2B66995EC0BB004B2729E54240
</code></pre><h3 id=summary><a href=#summary>Summary</a></h3><p>If you’re space limited, using a web API based geocoder might be the way to go. Using a geocoder function with triggers on new row inserts will get you geocoded addresses in a snap. ]]></content:encoded>
<category><![CDATA[ Postgres Tutorials ]]></category>
<category><![CDATA[ Spatial ]]></category>
<author><![CDATA[ Jacob.Coblentz@crunchydata.com (Jacob Coblentz) ]]></author>
<dc:creator><![CDATA[ Jacob Coblentz ]]></dc:creator>
<guid isPermalink="false">22e4658f5bcb8ab78691e22a40200dd500b79a366ed02108a2ac7e3c2f80cf94</guid>
<pubDate>Thu, 02 Mar 2023 11:00:00 EST</pubDate>
<dc:date>2023-03-02T16:00:00.000Z</dc:date>
<atom:updated>2023-03-02T16:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Fun with Letters in PostGIS 3.3! ]]></title>
<link>https://www.crunchydata.com/blog/fun-with-letters-in-postgis-33</link>
<description><![CDATA[ Check out the new PostGIS function, ST_Letters. We're making words, putting words on a map, increasing convexity with ST_ConcaveHull, and making letters from polygons with ST_TriangulatePolygon. ]]></description>
<content:encoded><![CDATA[ <p>Working at Crunchy Data on the <a href=https://www.crunchydata.com/products/crunchy-spatial>spatial</a> team, I'm always looking for new features and fun things to show on live demos. I recently started playing around with <code>ST_Letters</code> and wanted to jot down some quick code samples for playing around with this feature, introduced in PostGIS 3.3. These examples are super easy to use, they don't need any data!<p>The screenshots shown below came from pgAdmin's geometry viewer and will also work with other query GUI tools like QGIS or DBeaver.<h2 id=st_letters><a href=#st_letters><code>ST_Letters</code></a></h2><p>Here's a simple example to get started with <code>ST_Letters</code>. This will work on any Postgres database, running the PostGIS extension version 3.3+.<p><code>Select ST_Letters('PostGIS');</code> <img alt="postgis letters"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/3d57fa02-dc2f-4588-3bbd-8eecaa7f3800/public><p>It's also possible to overlay letters on a map, just like any other polygon. Since the default for <code>ST_Letters</code> results in a polygon starting at the baseline at the origin of the chosen projection, with a maximum height of 100 "units" (from the bottom of the descenders to the tops of the capitals).<p><img alt="letters on top"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/ab059cfa-242f-4454-29e4-e9c52342d000/public><p>That's not ideal. We need a way to both move it and resize it.<p>First, we want to make a point in the middle of San Francisco in order to serve as a centroid for where we want to move the letters, and we also want to rescale the letters in order to approximately fit over the City of San Francisco. Using the formula for converting units in WGS84 to meters, 0.001 works approximately well enough to fit over the San Francisco Bay Area.<p>Next we use <code>ST_Translate</code> in order to move the letters from the top of the map to fit over the Bay Area. Finally, mostly because it looks cool, we use <code>ST_Rotate</code> to rotate the polygon 45 degrees.<pre><code class=language-pgsql>WITH
san_fran_pt AS (
  SELECT ST_Point(-122.48, 37.758, 4326) AS geom),
letters AS (
  SELECT ST_Scale(ST_SetSRID(
           ST_Letters('San Francisco'), 4326),
           0.001, 0.001) AS geom),
letters_geom AS (
    SELECT ST_Translate(
            letters.geom,
            ST_X(san_fran_pt.geom) - ST_X(ST_Centroid(letters.geom)),
            ST_Y(san_fran_pt.geom) - ST_Y(ST_Centroid(letters.geom))
        ) AS geom
    FROM letters, san_fran_pt
)
SELECT ST_Rotate(geom, -pi() / 4, ST_Centroid(geom))
FROM letters_geom;
</code></pre><p><img alt="letters on map"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/7cef03b9-a5ad-46d2-692a-96b1fe1f4000/public><h2 id=st_concavehull-demod-with-st_letters><a href=#st_concavehull-demod-with-st_letters><code>ST_ConcaveHull</code> demo'd with <code>ST_Letters</code></a></h2><p>A great use case for <code>ST_Letters</code> is for demoing PostGIS functions. In this post, I'm going to demo the function <code>ST_ConcaveHull</code>, which creates a concave polygon which encloses the vertices of a target geometry. <code>ST_ConcaveHull</code> was recently updated in PostGIS 3.3.0, in order to use GEOS 3.11, which makes the input parameters easier to understand and results in a large speed upgrade. Here's a short demo of how different parameters of <code>param_pctconvex</code> and <code>param_allow_holes</code> for <code>ST_ConcaveHull</code> operate on points generated by <code>ST_GeneratePoints</code> and <code>ST_Letters</code>.<p>First, let's generate a table of randomly generated points that fill in the letters in 'postgis'.<pre><code class=language-pgsql>CREATE TABLE public.word_pts AS
WITH word AS (
  SELECT ST_Letters('postgis') AS geom
  ),
letters AS ( -- dump letter multipolygons into individual polygons
  SELECT (ST_Dump(word.geom)).geom
  FROM word
  )
SELECT
  letters.geom AS polys,
  ST_GeneratePoints(letters.geom, 100) AS pts
FROM letters;
</code></pre><p><code>SELECT pts FROM word_pts.pts</code><p><img alt="word points"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/9bbb26f9-d565-44ef-c326-e47b8f4db200/public><p>Then, we set the convexity to a fairly high parameter (<code>param_pctconvex=0.75</code>, indicating a highly convex shape), and don't allow there to be holes in the shape (<code>param_allow_holes=false</code>)<pre><code class=language-pgsql>SELECT ST_ConcaveHull(pts, 0.75, false) FROM word_pts;
</code></pre><p><img alt="concave .75"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/c519f214-ca0a-423a-9830-55c06304ef00/public><p>Doesn't look much like 'postgis'!<p>Next, we reduce the convexity, but don't allow holes in the shape.<pre><code class=language-pgsql>SELECT ST_ConcaveHull(pts, 0.5, false) FROM word_pts;
</code></pre><p><img alt="concave .5 false"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/5da500c1-993c-42f2-9b88-1e069d879200/public><p>A little better, but still hard to recognize 'postgis'. What if we allowed holes?<pre><code class=language-pgsql>SELECT ST_ConcaveHull(pts, 0.5, true) FROM word_pts;
</code></pre><p>This starts to look a bit more like the word 'postgis', with the hole in 'p' being clear.<p><img alt="concave .5"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/2b4fa888-5ee4-4649-2c2d-aedce5617500/public><p>As we start to make the shape more concave, it begins to take on more and more recognizable as 'postgis'....until it doesn't and starts to look closer to modern art.<pre><code class=language-pgsql>SELECT ST_ConcaveHull(pts, 0.35, true) FROM word_pts;
</code></pre><p><img alt="concave .35"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/7d381a72-44d3-4a77-f87f-a1056f489800/public><pre><code class=language-pgsql>SELECT ST_ConcaveHull(pts, 0.05, true) FROM word_pts;
</code></pre><p><img alt="concave .05"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/0af2371c-35c8-4459-607d-af9806ce2900/public><h2 id=polygons-too><a href=#polygons-too>Polygons too!</a></h2><p><code>ST_ConcaveHull</code> is also useful on multipolygons, and follows the same properties as demo'd on multipoints. It's important to note that if there are already holes in the existing multipolygon, setting <code>param_allow_holes=false</code> will still create convex polygons with "holes" in the middle, following the original polygon. The concave hulls will always contains the original polygons!<pre><code class=language-pgsql>SELECT ST_ConcaveHull(ST_Letters('postgis'), 0.5, false);
</code></pre><p><img alt="concave .5"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/dd8d022a-2fca-4ed6-ee0b-1976b3d10400/public><p>As the convexity decreases and holes are allowed, the shape looks more and more like the original polygons in the original table.<pre><code class=language-pgsql>SELECT ST_ConcaveHull(ST_Letters('postgis'), 0.1, true);
</code></pre><p><img alt="Concave .1"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/8a8af85b-03af-4ffb-f26c-94f957d0b200/public><h2 id=st_triangulatepolygon><a href=#st_triangulatepolygon><code>ST_TriangulatePolygon</code></a></h2><p>The last demo here is the function <code>ST_TriangulatePolygon</code>, new in PostGIS 3.3. This function computes the "best quality" triangulation of a polygon (and also works on multipolygons too!). This can be extremely useful for computing meshes of polygons in a quick and efficient manner.<pre><code class=language-pgsql>SELECT ST_TriangulatePolygon(ST_Letters('postgis'));
</code></pre><p><img alt=ST_TriangulatePolygon loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/51584339-bdda-4955-5742-6fb2583bc000/public><h2 id=summary><a href=#summary>Summary</a></h2><p><code>ST_Letters</code> provides a useful starting point for demoing functions on points and polygons. The new improvements in <code>ST_ConcaveHull</code> make it more useful for generating concave hulls of geometries and they are significantly more intuitive to use. <code>ST_TriangulatePolygon</code> can be useful for finding meshes of polygons and multipolygons. The team at Crunchy Data will continue to make important contributions to PostGIS in order to help our users create interesting and innovative open source solutions! ]]></content:encoded>
<category><![CDATA[ Spatial ]]></category>
<category><![CDATA[ Fun with Postgres ]]></category>
<author><![CDATA[ Jacob.Coblentz@crunchydata.com (Jacob Coblentz) ]]></author>
<dc:creator><![CDATA[ Jacob Coblentz ]]></dc:creator>
<guid isPermalink="false">d4ec3afc344817f044e31d4bbed7e2efdb720f511856de4d7ee9e19b0ef7f29b</guid>
<pubDate>Thu, 12 Jan 2023 10:00:00 EST</pubDate>
<dc:date>2023-01-12T15:00:00.000Z</dc:date>
<atom:updated>2023-01-12T15:00:00.000Z</atom:updated></item></channel></rss>