# frozen_string_literal: true require 'fileutils' require 'shellwords' require 'tmpdir' # tool to write ArcGIS layer(s) to json or database output # rubocop:disable Metrics/ClassLength class LayerWriter attr_reader :type GDAL = /GDAL (\d+\.\d+\.\d+)/ V1_11_4 = Gem::Version.new('1.11.4') # https://trac.osgeo.org/gdal/ticket/6529 TABLES = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'" TYPES = ['Group ', 'Feature ', 'Annotation ', 'Annotation Sub'].freeze CONN = %i[host port dbname user password].freeze GEOM_TYPES = { 'esriGeometryPoint' => 'POINT', 'esriGeometryMultipoint' => 'MULTIPOINT', 'esriGeometryLine' => 'LINESTRING', 'esriGeometryPolyline' => 'MULTILINESTRING', 'esriGeometryPolygon' => 'MULTIPOLYGON' }.freeze OGR = 'ogr2ogr -overwrite -f "PostgreSQL" PG:' def initialize(url:, path: nil, arcrest_opts: {}) @url = url @output_path = output_path(path) || config_path @arcrest_opts = arcrest_opts @conn = conn @id = id @service_url = service_url @layer = layer @page_json = @layer.json @type = layer_type @name = name end def output_json output(:json) end def output_to_db raise 'ogr2ogr executable missing, is GDAL installed and in your PATH?' unless (v_string = ogr2ogr?) raise 'ogr2ogr version must be > 1.11.4' unless Gem::Version.new(v_string.match(GDAL)[1]) > V1_11_4 output(:db) end private def conn CONN.zip(CONN.map { |key| GisScraper.config[key] }).to_h end def output(format) @type == 'Feature Layer' ? _method(format) : do_sub_layers(format) # recurses sub-layers end def _method(format) format == :db ? write_to_db : write_json end def output_path(path) File.expand_path(path) if path end def connection PG.connect @conn end def ogr2ogr? `ogr2ogr --version` rescue Errno::ENOENT nil end def config_path File.expand_path GisScraper.config[:output_path] end def service_url @url.split('/')[0..-2].join('/') end def id @url.split('/').last end def layer ArcREST::Layer.new(@url, @arcrest_opts) end def layer_type validate_layer @page_json['type'] end def validate_layer(type) raise "Bad Layer type: #{type}" unless TYPES.any? { |t| type == "#{t}Layer" } type end def name @page_json['name'].tr('/', '_') # make Postgres-safe end def sub_layer_ids @page_json['subLayers'].map { |hash| hash['id'] } || [] end def json_data FeatureScraper.new(url: "#{@service_url}/#{@id}", arcrest_opts: @arcrest_opts).json_data end def write_json File.write json_path, json_data end def json_path "#{@output_path}/#{@name}.json" end def write_to_db @output_path = Dir.mktmpdir('gis_scraper') # prefix for identification write_json `#{OGR}"#{conn_str}" "#{json_path}" -nln #{table} #{srs} -nlt #{pg_geom}` ensure FileUtils.remove_entry @output_path end def pg_geom GEOM_TYPES[geo] || raise("Unknown geom: '#{geo}' for layer #{@name}") end def geo @page_json['geometryType'] end def srs return '' unless GisScraper.config[:srs] "-a_srs #{GisScraper.config[:srs]}" || '' end def tables connection.exec(TABLES).map { |tup| tup['table_name'] } # list of current db table names end def table table_name << table_suffix end def table_name Shellwords.escape(@name.downcase.tr(' ', '_')).prepend('_') end def table_suffix tables.any? { |t| t == table_name } ? '_' : '' end def conn_str host, port, db, user, pwd = *@conn.values "dbname='#{db}' host='#{host}' port='#{port}' user='#{user}' password='#{pwd}'" end def do_sub_layers(format) FileUtils.mkdir File.join(@output_path, @name) if format == :json path = @output_path << "/#{@name}" sub_layer_ids.each { |n| sub_layer(n, path).send(:output, format) } end def sub_layer(id, path) self.class.new(url: "#{@service_url}/#{id}", path: path, arcrest_opts: @arcrest_opts) # recurse end end # rubocop:enable Metrics/ClassLength