Skip to content

Spatial Factory Store

Keith Doggett edited this page Dec 15, 2020 · 1 revision

What is the SpatialFactoryStore

The SpatialFactoryStore is a singleton used to convert geometries stored in your database into RGeo objects that you can work with in your Ruby application. In most spatial databases, geometries are stored in a format called Well-Known Binary (WKB). This is a way to represent all geometries as hex data and is not really useful in an application. RGeo comes with a WKBParser that can be used to parse this text, but the SpatialFactoryStore allows for this to be done automatically and let the user work on their application not worry about formatting geometries.

The SpatialFactoryStore works by associating RGeo factories with different attributes used to describe geometry/geography columns. For instance, it allows you to parse all geographic columns with an RGeo::Geographic.spherical_factory and all geometric columns with an RGeo::Cartesian.preferred_factory. Or going even further, all line_string columns could be parsed with a cartesian factory while points use the spherical factory.

Default Factory

By default, the SpatialFactoryStore has defaults baked-in: all geometry columns will use RGeo::Cartesian.preferred_factory and all geography columns will use RGeo::Geographic.spherical_factory.

You can override the default factory with the following:

# assign default to whatever factory you want
RGeo::ActiveRecord::SpatialFactoryStore.instance.default = RGeo::Geos.factory(srid: 3857)

So any column that cannot find a match in the registry will fall back to this default.

Adding Factories

The SpatialFactoryStore uses a registry that matches factories to columns by finding the entry with the most matching columns without any mismatches. When column information is sent to the store, it is a hash with the following keys:

geo_type: string  # geometry, point, polygon, line_string, geometry_collection,
                  # multi_line_string, multi_point, multi_polygon
has_m:    boolean # true, false
has_z:    boolean # true, false
sql_type: string  # geometry, geography
srid:     int     # (any valid SRID)

These are also the keys that can be used when registering a factory to describe what it should match. For example:

RGeo::ActiveRecord::SpatialFactoryStore.instance.tap do |store|
  # register a factory for all point columns
  store.register(RGeo::Cartesian.preferred_factory(srid: 4055), {geo_type: "point"})

  # register a factory for all point columns with srid 4326
  store.register(RGeo::Cartesian.preferred_factory(srid: 4326), {geo_type: "point", srid: 4326})
end

In this example, here's what the following columns would return:

{geo_type: point, has_m: false, 
 has_z: false, sql_type: geography, 
 srid: 3857
}
#=> The first factory because it is a point, but does not have srid 4326, so it mismatches with the second factory.

{geo_type: point, has_m: false, 
 has_z: false, sql_type: geography, 
 srid: 4326
}
#=> The second factory because it is a point and has srid 4326, so it has 2 matching attributes with factory2
    which is more than factory1 which it has 1 match with.

{geo_type: line_string, has_m: false, 
 has_z: false, sql_type: geography, 
 srid: 4326
}
#=> The default factory because it mismatches the geo_type attribute with both and would fall back to the default.

Order Matters

The SpatialFactoryStore will return the factory with the most matches, but if there are multiple factories tied for the most, the first entry in the registry will be the factory returned.

Usage with Rails

The best way to set up a registry in Rails is to create your registry in an initializer.

For example:

# config/initializers/rgeo_factories.rb
RGeo::ActiveRecord::SpatialFactoryStore.instance.tap do |store|
  store.default = RGeo::Cartesian.preferred_factory

  store.register(RGeo::Geographic.spherical_factory(srid: 4326), {geo_type: "point", srid: 4326, sql_type: "geography"}) 
end