Updated Optimize Rendering with PostGIS (markdown)
parent
c406aca379
commit
f03b4ca77f
1 changed files with 49 additions and 46 deletions
|
@ -17,17 +17,18 @@ First, you have to understand how the Datasource table parameter works for each
|
||||||
|
|
||||||
Often, when making a new layer and not paying much attention to the Datasource, you may be doing:
|
Often, when making a new layer and not paying much attention to the Datasource, you may be doing:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Parameter name="table">planet_osm_point</Parameter>
|
<Parameter name="table">planet_osm_point</Parameter>
|
||||||
|
```
|
||||||
|
|
||||||
This is fine, and will get your data from the database. In fact, it will get *all* data from that table that is within the area that you're rendering. Taking the above explanation, Mapnik expands this to:
|
This is fine, and will get your data from the database. In fact, it will get *all* data from that table that is within the area that you're rendering. Taking the above explanation, Mapnik expands this to:
|
||||||
|
|
||||||
|
|
||||||
#!sql
|
```sql
|
||||||
SELECT AsBinary("way") AS geom
|
SELECT AsBinary("way") AS geom
|
||||||
FROM planet_osm_point
|
FROM planet_osm_point
|
||||||
WHERE "way" && SetSRID('BOX3D(-39135.75848201022 6222585.598639628,665307.8941941741 6927029.251315812)'::box3d,900913)
|
WHERE "way" && SetSRID('BOX3D(-39135.75848201022 6222585.598639628,665307.8941941741 6927029.251315812)'::box3d,900913)
|
||||||
|
```
|
||||||
|
|
||||||
The SELECT will also add all columns needed to satisfy the styles for this layer. The "WHERE ..." ending will only return rows with an actual geometry (or there'd be nothing to draw for that row anyway) limited to the BBOX given. The coordinates above are just an example.
|
The SELECT will also add all columns needed to satisfy the styles for this layer. The "WHERE ..." ending will only return rows with an actual geometry (or there'd be nothing to draw for that row anyway) limited to the BBOX given. The coordinates above are just an example.
|
||||||
|
|
||||||
|
@ -38,52 +39,52 @@ This type of query is not very efficient. Other than the check for a valid geome
|
||||||
|
|
||||||
Let's say you have a style that's only rendering place names for a given layer. You're not rendering anything else in this style, so why would you fetch records from the database for this layer that have nothing to do with place names? Let's limit the query to just what we need:
|
Let's say you have a style that's only rendering place names for a given layer. You're not rendering anything else in this style, so why would you fetch records from the database for this layer that have nothing to do with place names? Let's limit the query to just what we need:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<!-- the table parameter for a Layer with just place name styles associated with it -->
|
<!-- the table parameter for a Layer with just place name styles associated with it -->
|
||||||
<Parameter name="table">(select * from planet_osm_point where place is not null) as foo</Parameter>
|
<Parameter name="table">(select * from planet_osm_point where place is not null) as foo</Parameter>
|
||||||
|
```
|
||||||
|
|
||||||
This is an example of a subquery. Mapnik will still put its own SELECT and WHERE "way" etc. around it, and PostGIS will actually see this query:
|
This is an example of a subquery. Mapnik will still put its own SELECT and WHERE "way" etc. around it, and PostGIS will actually see this query:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
SELECT AsBinary("way") AS geom from
|
SELECT AsBinary("way") AS geom from
|
||||||
(select * from planet_osm_point where place is not null) as foo
|
(select * from planet_osm_point where place is not null) as foo
|
||||||
WHERE "way" && SetSRID('BOX3D(-39135.75848201022 6222585.598639628,665307.8941941741 6927029.251315812)'::box3d,900913)
|
WHERE "way" && SetSRID('BOX3D(-39135.75848201022 6222585.598639628,665307.8941941741 6927029.251315812)'::box3d,900913)
|
||||||
|
```
|
||||||
|
|
||||||
Let's say you're filtering for some water features, and only want to render rivers, canals, drains, and streams:
|
Let's say you're filtering for some water features, and only want to render rivers, canals, drains, and streams:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT * from planet_osm_line where waterway in ('river','canal','drain','stream')) as foo
|
(SELECT * from planet_osm_line where waterway in ('river','canal','drain','stream')) as foo
|
||||||
|
```
|
||||||
|
|
||||||
If you only want to render road tunnels in a style:
|
If you only want to render road tunnels in a style:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT * from planet_osm_line where highway is not null and tunnel in ('yes','true','1')) as foo
|
(SELECT * from planet_osm_line where highway is not null and tunnel in ('yes','true','1')) as foo
|
||||||
|
```
|
||||||
|
|
||||||
### Limiting the columns fetched from the database
|
### Limiting the columns fetched from the database
|
||||||
|
|
||||||
Doing a `SELECT *` will fetch all columns for the records you want. You can further limit this to only the fields you need in your style.
|
Doing a `SELECT *` will fetch all columns for the records you want. You can further limit this to only the fields you need in your style.
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(select way,place from planet_osm_point where place is not null) as foo
|
(select way,place from planet_osm_point where place is not null) as foo
|
||||||
|
```
|
||||||
|
|
||||||
Notice the `way` column. You always need to include this, or else Mapnik will not get the geometry from the database, and nothing can be drawn.
|
Notice the `way` column. You always need to include this, or else Mapnik will not get the geometry from the database, and nothing can be drawn.
|
||||||
|
|
||||||
Assume you're also testing for capital cities. You're not testing for `capital` in the SQL, but you will be testing for that in the Mapnik rules. That means the `capital` field needs to be included in the results, by putting it into the `SELECT`:
|
Assume you're also testing for capital cities. You're not testing for `capital` in the SQL, but you will be testing for that in the Mapnik rules. That means the `capital` field needs to be included in the results, by putting it into the `SELECT`:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT way,place,capital from planet_osm_point where place is not null) as foo
|
(SELECT way,place,capital from planet_osm_point where place is not null) as foo
|
||||||
|
```
|
||||||
|
|
||||||
You may be building some rules that render elements based on the length of a field, let's say for different sized highway shields:
|
You may be building some rules that render elements based on the length of a field, let's say for different sized highway shields:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT way,highway,ref,char_length(ref) as length from planet_osm_line where highway is not null and ref is not null) as foo
|
(SELECT way,highway,ref,char_length(ref) as length from planet_osm_line where highway is not null and ref is not null) as foo
|
||||||
|
```
|
||||||
|
|
||||||
Notice again that you only need to fetch data from the database that will actually render for this style. Assuming that you're rendering highway shields, it makes no sense to fetch highways without a _ref_.
|
Notice again that you only need to fetch data from the database that will actually render for this style. Assuming that you're rendering highway shields, it makes no sense to fetch highways without a _ref_.
|
||||||
|
|
||||||
|
@ -91,38 +92,40 @@ Notice again that you only need to fetch data from the database that will actual
|
||||||
|
|
||||||
Often, a Mapnik style will only render a certain element. You would normally use a filter to only draw something for those elements:
|
Often, a Mapnik style will only render a certain element. You would normally use a filter to only draw something for those elements:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[power] = 'tower'</Filter>
|
<Filter>[power] = 'tower'</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
used together with:
|
used together with:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(select * from planet_osm_point) as foo
|
(select * from planet_osm_point) as foo
|
||||||
|
```
|
||||||
|
|
||||||
But this means that you a) select all rows from the database from that table and b) Mapnik then needs to discard most of those rows. Same as in the examples above. If your style only contains a single type of data to render, you can omit the Mapnik filtering altogether and do all filtering in SQL:
|
But this means that you a) select all rows from the database from that table and b) Mapnik then needs to discard most of those rows. Same as in the examples above. If your style only contains a single type of data to render, you can omit the Mapnik filtering altogether and do all filtering in SQL:
|
||||||
|
|
||||||
|
|
||||||
#!sql
|
```sql
|
||||||
(SELECT way,power from planet_osm_point where power='tower') as foo
|
(SELECT way,power from planet_osm_point where power='tower') as foo
|
||||||
|
```
|
||||||
|
|
||||||
You may be rendering only tunnels for roads in a particular style:
|
You may be rendering only tunnels for roads in a particular style:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT way,highway from planet_osm_line where highway is not null and tunnel in ('yes','true','1')) as foo
|
(SELECT way,highway from planet_osm_line where highway is not null and tunnel in ('yes','true','1')) as foo
|
||||||
|
```
|
||||||
|
|
||||||
Then you don't need to include a filter to test for tunnel in the Mapnik rules. Usually this means you can simplify things:
|
Then you don't need to include a filter to test for tunnel in the Mapnik rules. Usually this means you can simplify things:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[highway] = 'motorway' and ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
<Filter>[highway] = 'motorway' and ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
becomes
|
becomes
|
||||||
|
|
||||||
<Filter>[highway] = 'motorway'</Filter>
|
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Filter>[highway] = 'motorway'</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
## Simplify redundant filter elements
|
## Simplify redundant filter elements
|
||||||
|
|
||||||
|
@ -130,52 +133,52 @@ Let's elaborate some more on the last filter example. You may have a style that'
|
||||||
|
|
||||||
So you'll see a mix of filters in the style:
|
So you'll see a mix of filters in the style:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[highway] = 'motorway' and ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
<Filter>[highway] = 'motorway' and ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
and another rule with
|
and another rule with
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[highway] = 'motorway' and not ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
<Filter>[highway] = 'motorway' and not ([tunnel] = 'yes' or [tunnel] = 'true' or [tunnel] = '1')</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
Typing all those tunnel tests (actually three tests rolled into one) will get tiresome after a while, and they don't add to the readability of the XML.
|
Typing all those tunnel tests (actually three tests rolled into one) will get tiresome after a while, and they don't add to the readability of the XML.
|
||||||
|
|
||||||
SQL to the rescue! Let's have postgresql roll all those choices into 1 simple one to work with in the Mapnik filters:
|
SQL to the rescue! Let's have postgresql roll all those choices into 1 simple one to work with in the Mapnik filters:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT way,highway,
|
(SELECT way,highway,
|
||||||
case when tunnel in ('yes','true','1') then 'yes'::text
|
case when tunnel in ('yes','true','1') then 'yes'::text
|
||||||
else tunnel
|
else tunnel
|
||||||
end as tunnel
|
end as tunnel
|
||||||
from planet_osm_line where highway is not null) as foo
|
from planet_osm_line where highway is not null) as foo
|
||||||
|
```
|
||||||
|
|
||||||
The query is split over multiple lines so it's easy to see what's going on. You can also split your own queries into multiple lines in the stylesheet (Mapnik 0.6.0 > | see #173), if it helps you to understand long queries.
|
The query is split over multiple lines so it's easy to see what's going on. You can also split your own queries into multiple lines in the stylesheet (Mapnik 0.6.0 > | see #173), if it helps you to understand long queries.
|
||||||
|
|
||||||
Now you can replace the two filters with these:
|
Now you can replace the two filters with these:
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[highway] = 'motorway' and [tunnel] = 'yes'</Filter>
|
<Filter>[highway] = 'motorway' and [tunnel] = 'yes'</Filter>
|
||||||
|
|
||||||
<Filter>[highway] = 'motorway' and not [tunnel] = 'yes'</Filter>
|
<Filter>[highway] = 'motorway' and not [tunnel] = 'yes'</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
You could even expand the `case when ... end` to also handle the `no` cases, and increase the readability some more.
|
You could even expand the `case when ... end` to also handle the `no` cases, and increase the readability some more.
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
(SELECT way,highway,
|
(SELECT way,highway,
|
||||||
case when tunnel in ('yes','true','1') then 'yes'::text
|
case when tunnel in ('yes','true','1') then 'yes'::text
|
||||||
else 'no'::text
|
else 'no'::text
|
||||||
end as tunnel
|
end as tunnel
|
||||||
from planet_osm_line where highway is not null) as foo
|
from planet_osm_line where highway is not null) as foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
#!xml
|
|
||||||
<Filter>[highway] = 'motorway' and [tunnel] = 'yes'</Filter>
|
<Filter>[highway] = 'motorway' and [tunnel] = 'yes'</Filter>
|
||||||
<Filter>[highway] = 'motorway' and [tunnel] = 'no'</Filter>
|
<Filter>[highway] = 'motorway' and [tunnel] = 'no'</Filter>
|
||||||
|
```
|
||||||
|
|
||||||
In these examples, the SQL will get more elaborate, but actual filters will be greatly simplified. Since there is only a single datasource query for a layer, and potentially lots of rules and filters to test these results, it stands to reason to do the hard work in a single location, and leave it to the component that does this the best: PostGIS.
|
In these examples, the SQL will get more elaborate, but actual filters will be greatly simplified. Since there is only a single datasource query for a layer, and potentially lots of rules and filters to test these results, it stands to reason to do the hard work in a single location, and leave it to the component that does this the best: PostGIS.
|
||||||
|
|
||||||
|
@ -183,22 +186,22 @@ In these examples, the SQL will get more elaborate, but actual filters will be g
|
||||||
|
|
||||||
Creating indexes can also help fetching the rows faster. To create a GIST index:
|
Creating indexes can also help fetching the rows faster. To create a GIST index:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
CREATE INDEX idx_buildings_the_geom ON buildings USING gist(the_geom);
|
CREATE INDEX idx_buildings_the_geom ON buildings USING gist(the_geom);
|
||||||
|
```
|
||||||
|
|
||||||
Also, if you are filtering or sorting on a specific field, an index on that field can help too:
|
Also, if you are filtering or sorting on a specific field, an index on that field can help too:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
CREATE INDEX idx_buildings_code ON buildings USING btree(code);
|
CREATE INDEX idx_buildings_code ON buildings USING btree(code);
|
||||||
|
```
|
||||||
|
|
||||||
## General Postgresql maintenance
|
## General Postgresql maintenance
|
||||||
|
|
||||||
Keep your database optimized. You should run this SQL command from time to time:
|
Keep your database optimized. You should run this SQL command from time to time:
|
||||||
|
|
||||||
|
```sql
|
||||||
#!sql
|
|
||||||
vacuum full analyze
|
vacuum full analyze
|
||||||
|
```
|
||||||
|
|
||||||
If there is any active connection Postgresql will wait until it is closed, so if you are running Ogcserver restart Apache to close the connections.
|
If there is any active connection Postgresql will wait until it is closed, so if you are running Ogcserver restart Apache to close the connections.
|
Loading…
Add table
Reference in a new issue