Class: NSWTopo::Shapefile::Layer

Inherits:
Object
  • Object
show all
Defined in:
lib/nswtopo/gis/shapefile.rb

Constant Summary collapse

NoLayerError =
Class.new RuntimeError

Instance Method Summary collapse

Constructor Details

#initialize(source, layer: nil, where: nil, fields: nil, sql: nil, geometry: nil, projection: nil) ⇒ Layer

Returns a new instance of Layer.



40
41
42
# File 'lib/nswtopo/gis/shapefile.rb', line 40

def initialize(source, layer: nil, where: nil, fields: nil, sql: nil, geometry: nil, projection: nil)
  @source, @layer, @where, @fields, @sql, @geometry, @projection = source, layer, where, fields, sql, geometry, projection
end

Instance Method Details

#countsObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/nswtopo/gis/shapefile.rb', line 60

def counts
  raise NoLayerError, "no layer name provided" unless @layer
  count = ?_ * @fields.map(&:size).max + "count"
  where = %Q[WHERE (%s)] % [*@where].join(") AND (") if @where
  field_list = %Q["%s"] % @fields.join('", "')
  sql = <<~SQL % [field_list, count, @layer, where, field_list]
    SELECT %s, count(*) AS "%s"
    FROM "%s"
    %s
    GROUP BY %s
  SQL
  json = OS.ogr2ogr *%w[-f GeoJSON -lco RFC7946=NO -dialect sqlite -sql], sql, "/vsistdout/", @source.path
  JSON.parse(json)["features"].map do |feature|
    feature["properties"]
  end.map do |properties|
    [properties.slice(*@fields), properties[count]]
  end
rescue OS::Error => error
  raise unless /no such column: (.*)$/ === error.message
  raise "invalid field: #{$1}"
end

#featuresObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/nswtopo/gis/shapefile.rb', line 44

def features
  raise "can't specify both SQL and where clause" if @sql && @where
  raise "can't specify both SQL and layer name" if @sql && @layer
  raise "no layer name or SQL specified" unless @layer || @sql
  sql   = ["-sql", sql] if @sql
  where = ["-where", "(" << Array(@where).join(") AND (") << ")"] if @where
  srs   = ["-t_srs", @projection] if @projection
  spat  = ["-spat", *@geometry.bounds.transpose.flatten, "-spat_srs", @geometry.projection] if @geometry
  misc  = %w[-mapFieldType Date=Integer,DateTime=Integer -dim XY]
  json = OS.ogr2ogr *(sql || where), *srs, *spat, *misc, *%w[-f GeoJSON -lco RFC7946=NO /vsistdout/], @source.path, *@layer
  GeoJSON::Collection.load json, **{projection: @projection}.compact
rescue OS::Error => error
  raise unless /Couldn't fetch requested layer (.*)!/ === error.message
  raise "no such layer: #{$1}"
end

#infoObject



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/nswtopo/gis/shapefile.rb', line 82

def info
  raise NoLayerError, "no layer name provided" unless @layer
  info = OS.ogrinfo *%w[-ro -so -noextent], @source.path, @layer
  geom_type = info.match(/^Geometry: (.*)$/)&.[](1)&.delete(?\s)
  count = info.match(/^Feature Count: (\d+)$/)&.[](1)
  fields = info.scan(/^(.*): (.*?) \(\d+\.\d+\)$/).to_h
  wkt = info.each_line.slice_after(/^Layer SRS WKT:/).drop(1).first&.slice_before(/^\S/)&.first&.join
  epsg = OS.gdalsrsinfo("-o", "epsg", wkt)[/\d+/] if wkt and !wkt["unknown"]
  { name: @layer, geometry: geom_type, EPSG: epsg, features: count, fields: (fields unless fields.empty?) }.compact
rescue OS::Error => error
  raise unless /Couldn't fetch requested layer (.*)!/ === error.message
  raise "no such layer: #{$1}"
end