Skip to main content

Tutorial: Switching from add_row() to insert()

In 1.7.0 we introduced a new way of doing geometry transformations in flex config files. Instead of declaring a geometry transformation in the add_row() function (or using the default one) you have to explicitly say how to transform the geometry and then use that like any normal value in the insert() function. This is a tiny bit more work, but it make is much more obvious what’s happening. And it gives you more options, allowing you to do transformations which weren’t possible before.

This document described how to change flex config files which are using the old add_row() method to the new way of doing things with insert().

The add_row() function is deprecated now and will be removed in a future version of osm2pgsql.

For Point Layers

For Point layers it is easy to switch. Lets say you have a table that should get all names and locations of restaurants. Change the add_row to insert and the { create = 'point' } to object:as_point(). Because { create = 'point' } is the default, you might not have that line at all. But you still need the new one.

local restaurants = osm2pgsql.define_node_table('restaurants', {
    { column = 'name', type = 'text' },
    { column = 'geom', type = 'point' }
})

function osm2pgsql.process_node(object)
    if object.tags.amenity == 'restaurant' then
       restaurants:add_row({
       restaurants:insert({
            name = object.tags.name,
            geom = { create = 'point' }
            geom = object:as_point()
        })
    end
end

For (Multi)LineString Layers

For LineString geometries from ways, you have to use the: as_linestring() function. Because { create = 'linestring' } is the default for LineString geometries, you might not have that line at all. But you still need the new one.

local railroads = osm2pgsql.define_way_table('railroads', {
    { column = 'geom', type = 'linestring' }
})

function osm2pgsql.process_way(object)
    if object.tags.railway == 'rail' then
        railroads:add_row({ geom = { create = 'line' } })
        railroads:insert({ geom = object:as_linestring() })
    end
end

If you were using split_at to split LineStrings into shorter ones, you’d magically get multiple rows added to your database for long LineStrings.

With the insert() function this is no longer the case. You explicitly have to call segmentize() now and then iterate over the results. Note that geometries are in 4326 (lat/lon) coordinates, so you have to convert them to 3857 (WebMercator) coordinates first, before calling segmentize() to achieve the same result as before.

local railroads = osm2pgsql.define_way_table('railroads', {
    { column = 'geom', type = 'linestring' }
})

function osm2pgsql.process_way(object)
    if object.tags.railway == 'rail' then
        railroads:add_row({
            geom = { create = 'line', split_at = 100 }
        })
        local mgeom = object:as_linestring():transform(3857):segmentize(100)
        for sgeom in mgeom:geometries() do
            railroads:insert({ geom = sgeom })
        end
    end
end

If you have a MultiLineString geometry you can feed it the split geometry directly:

local railroads = osm2pgsql.define_way_table('railroads', {
    { column = 'geom', type = 'multilinestring' }
})

function osm2pgsql.process_way(object)
    if object.tags.railway == 'rail' then
        railroads:add_row({ geom = { create = 'line', split_at = 100 } })
        railroads:insert({ geom = object:as_linestring():transform(3857):segmentize(100) })
    end
end

For Polygon Layers

If the (closed) way should be interpreted as a Polygon, the { create = 'area' } has to be replaced by a call to as_polygon(). Because { create = 'area' } is the default for Polygon geometries, you might not have that line at all. But you still need the new one.

local forests = osm2pgsql.define_way_table('forests', {
    { column = 'geom', type = 'polygon' }
})

function osm2pgsql.process_way(object)
    if object.is_closed and object.tags.natural == 'wood' then
        forests:add_row({ geom = { create = 'area' } })
        forests:insert({ geom = object:as_polygon() })
    end
end

If you are creating (Multi)Polygons from relations, you had to set { create = 'area' } explicitly. This is now replaced by a call to as_multipolygon().

local forests = osm2pgsql.define_area_table('forests', {
    { column = 'geom', type = 'multipolygon' }
})

function osm2pgsql.process_relation(object)
    if object.tags.type == 'multipolygon' and object.tags.natural == 'wood' then
         forests:add_row({ geom = { create = 'area' } })
         forests:insert({ geom = object:as_multipolygon() })
    end
end

If you were using split_at = 'multi', do the split explicitly now by iterating over the polygons in the multipolygon:

local forests = osm2pgsql.define_area_table('forests', {
    { column = 'geom', type = 'polygon' }
})

function osm2pgsql.process_relation(object)
    if object.tags.type == 'multipolygon' and object.tags.natural == 'wood' then
        forests:add_row({
            geom = { create = 'area', split_at = 'multi' }
        })
        local mgeom = object:as_multipolygon()
        for sgeom in mgeom:geometries() do
            forests:insert({ geom = sgeom })
        end
    end
end

The “Magic” Area Data Type

Sometimes you want a column with the area of a polygon. In the old way of doing things you used a special column of type area for this and osm2pgsql would magically fill in the area of the corresponding polygon. For the new way you have to use a normal column type, probably real and explicitly call the area() function on the polygon object to calculate the area.

local forests = osm2pgsql.define_way_table('forests', {
    { column = 'geom', type = 'polygon' },
    { column = 'area', type = 'area' }
    { column = 'area', type = 'real' }
})

function osm2pgsql.process_way(object)
    if object.is_closed and  object.tags.natural == 'wood' then
        forests:add_row({ geom = { create = 'area' } })
        local poly = object:as_polygon():transform(3857)
        forests:insert({ geom = poly, area = poly:area() })
    end
end

Note that we need to transform the geometry into WebMercator (3857) coordinates before calculating the area to get the same effect as before. We store the transformed geometry in a local variable so we don’t have to do the creation and transformation of the polygon geometry twice.

Because the area calculation is now explicit, you can decide how to calculate the area. If you want to have it in lat/lon coordinates instead, remove the transform(). Or you can use the spherical_area() function (available in osm2pgsql 1.9.0 or greater) instead.

local forests = osm2pgsql.define_way_table('forests', {
    { column = 'geom', type = 'polygon' },
    { column = 'area', type = 'real' }
})

function osm2pgsql.process_way(object)
    if object.is_closed and  object.tags.natural == 'wood' then
        local poly = object:as_polygon()
        forests:insert({ geom = poly, area = poly:spherical_area() })
    end
end

If you are splitting a multipolygon into its constituent polygons, you can calculate the area on the whole or on the parts:

local forests = osm2pgsql.define_area_table('forests', {
    { column = 'geom', type = 'polygon' },
    { column = 'marea', type = 'real' },
    { column = 'sarea', type = 'real' }
})

function osm2pgsql.process_relation(object)
    if object.tags.type == 'multipolygon' and object.tags.natural == 'wood' then
        local mgeom = object:as_multipolygon():transform(3857)
        local marea = mgeom:area()
        for sgeom in mgeom:geometries() do
            forests:insert({ geom = sgeom, marea = marea, sarea = sgeom:area() })
        end
    end
end

NULL Geometries

The old code with add_row() did never generate NULL values in the geometry columns, it just suppressed those rows entirely. This has changed with insert() because now a table can have multiple geometry columns, so a situation could arise where some of those columns are NULL and others have a valid geometry. If you want the old behaviour, you have to declare the geometry columns as NOT NULL.

Example:

local forests = osm2pgsql.define_area_table('forests', {
 { column = 'geom', type = 'polygon' }
 { column = 'geom', type = 'polygon', not_null = true }
})