# frozen_string_literal: true
# -----------------------------------------------------------------------------
#
# Well-known text generator for RGeo
#
# -----------------------------------------------------------------------------
module RGeo
module WKRep
# This class provides the functionality of serializing a geometry as
# WKT (well-known text) format. You may also customize the serializer
# to generate PostGIS EWKT extensions to the output, or to follow the
# Simple Features Specification 1.2 extensions for Z and M.
#
# To use this class, create an instance with the desired settings and
# customizations, and call the generate method.
#
# === Configuration options
#
# The following options are recognized. These can be passed to the
# constructor, or set on the object afterwards.
#
# [:tag_format]
# The format for tags. Possible values are :wkt11,
# indicating SFS 1.1 WKT (i.e. no Z or M markers in the tags) but
# with Z and/or M values added in if they are present;
# :wkt11_strict, indicating SFS 1.1 WKT with Z and M
# dropped from the output (since WKT strictly does not support
# the Z or M dimensions); :ewkt, indicating the PostGIS
# EWKT extensions (i.e. "M" appended to tag names if M but not
# Z is present); or :wkt12, indicating SFS 1.2 WKT
# tags that indicate the presence of Z and M in a separate token.
# Default is :wkt11.
# This option can also be specified as :type_format.
# [:emit_ewkt_srid]
# If true, embed the SRID of the toplevel geometry. Available only
# if :tag_format is :ewkt. Default is false.
# [:square_brackets]
# If true, uses square brackets rather than parentheses.
# Default is false.
# [:convert_case]
# Possible values are :upper, which changes all letters
# in the output to ALL CAPS; :lower, which changes all
# letters to lower case; or nil, indicating no case changes from
# the default (which is not specified exactly, but is chosen by the
# generator to emphasize readability.) Default is nil.
class WKTGenerator
# Create and configure a WKT generator. See the WKTGenerator
# documentation for the options that can be passed.
def initialize(opts = {})
@tag_format = opts[:tag_format] || opts[:type_format] || :wkt11
@emit_ewkt_srid = @tag_format == :ewkt ?
(opts[:emit_ewkt_srid] ? true : false) : nil
@square_brackets = opts[:square_brackets] ? true : false
@convert_case = opts[:convert_case]
end
# Returns the format for type tags. See WKTGenerator for details.
attr_reader :tag_format
alias type_format tag_format
# Returns whether SRID is embedded. See WKTGenerator for details.
def emit_ewkt_srid?
@emit_ewkt_srid
end
# Returns whether square brackets rather than parens are output.
# See WKTGenerator for details.
def square_brackets?
@square_brackets
end
# Returns the case for output. See WKTGenerator for details.
attr_reader :convert_case
def properties
{
"tag_format" => @tag_format.to_s,
"emit_ewkt_srid" => @emit_ewkt_srid,
"square_brackets" => @square_brackets,
"convert_case" => @convert_case ? @convert_case.to_s : nil
}
end
# Generate and return the WKT format for the given geometry object,
# according to the current settings.
def generate(obj)
@begin_bracket = @square_brackets ? "[" : "("
@end_bracket = @square_brackets ? "]" : ")"
factory = obj.factory
if @tag_format == :wkt11_strict
@cur_support_z = nil
@cur_support_m = nil
else
@cur_support_z = factory.property(:has_z_coordinate)
@cur_support_m = factory.property(:has_m_coordinate)
end
str = generate_feature(obj, true)
if @convert_case == :upper
str.upcase
elsif @convert_case == :lower
str.downcase
else
str
end
end
private
def generate_feature(obj, toplevel = false)
type = obj.geometry_type
type = Feature::LineString if type.subtype_of?(Feature::LineString)
tag = type.type_name.dup
if @tag_format == :ewkt
tag << "M" if @cur_support_m && !@cur_support_z
tag = "SRID=#{obj.srid};#{tag}" if toplevel && @emit_ewkt_srid
elsif @tag_format == :wkt12
if @cur_support_z
if @cur_support_m
tag << " ZM"
else
tag << " Z"
end
elsif @cur_support_m
tag << " M"
end
end
if type == Feature::Point
"#{tag} #{generate_point(obj)}"
elsif type == Feature::LineString
"#{tag} #{generate_line_string(obj)}"
elsif type == Feature::Polygon
"#{tag} #{generate_polygon(obj)}"
elsif type == Feature::GeometryCollection
"#{tag} #{generate_geometry_collection(obj)}"
elsif type == Feature::MultiPoint
"#{tag} #{generate_multi_point(obj)}"
elsif type == Feature::MultiLineString
"#{tag} #{generate_multi_line_string(obj)}"
elsif type == Feature::MultiPolygon
"#{tag} #{generate_multi_polygon(obj)}"
else
raise Error::ParseError, "Unrecognized geometry type: #{type}"
end
end
def generate_coords(obj)
str = +"#{obj.x} #{obj.y}"
str << " #{obj.z}" if @cur_support_z
str << " #{obj.m}" if @cur_support_m
str
end
def generate_point(obj)
"#{@begin_bracket}#{generate_coords(obj)}#{@end_bracket}"
end
def generate_line_string(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{obj.points.map { |p| generate_coords(p) }.join(', ')}#{@end_bracket}"
end
end
def generate_polygon(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{([generate_line_string(obj.exterior_ring)] + obj.interior_rings.map { |r| generate_line_string(r) }).join(', ')}#{@end_bracket}"
end
end
def generate_geometry_collection(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{obj.map { |f| generate_feature(f) }.join(', ')}#{@end_bracket}"
end
end
def generate_multi_point(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{obj.map { |f| generate_point(f) }.join(', ')}#{@end_bracket}"
end
end
def generate_multi_line_string(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{obj.map { |f| generate_line_string(f) }.join(', ')}#{@end_bracket}"
end
end
def generate_multi_polygon(obj)
if obj.is_empty?
"EMPTY"
else
"#{@begin_bracket}#{obj.map { |f| generate_polygon(f) }.join(', ')}#{@end_bracket}"
end
end
end
end
end