module NSWTopo::ArcGISServer
Constants
- ERRORS
- Error
- SERVICE
Public Class Methods
===(string)
click to toggle source
# File lib/nswtopo/gis/arcgis_server.rb, line 19 def self.===(string) uri, service_path, id = check_uri string uri != nil end
check_uri(url)
click to toggle source
# File lib/nswtopo/gis/arcgis_server.rb, line 9 def self.check_uri(url) uri = URI.parse url return unless URI::HTTP === uri instance, (id, *) = uri.path.split(?/).slice_after(SERVICE).take(2) return unless instance.last =~ SERVICE return unless !id || id =~ /^\d+$/ return uri, instance.join(?/), id rescue URI::Error end
start(url) { |connection, service, projection, *id| ... }
click to toggle source
# File lib/nswtopo/gis/arcgis_server.rb, line 24 def self.start(url, &block) uri, service_path, id = check_uri url raise "invalid ArcGIS server URL: %s" % url unless uri Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: 600) do |http| connection = Connection.new http, service_path service = connection.get_json projection = case when wkt = service.dig("spatialReference", "wkt") then Projection.new(wkt) when wkid = service.dig("spatialReference", "latestWkid") then Projection.new("EPSG:#{wkid}") when wkid = service.dig("spatialReference", "wkid") then Projection.new("EPSG:#{wkid == 102100 ? 3857 : wkid}") else raise Error, "no spatial reference found: #{uri}" end yield connection, service, projection, *id end rescue *ERRORS => error raise Error, error.message end
Public Instance Methods
arcgis_layer(url, where: nil, layer: nil, per_page: nil, margin: {}) { |total| ... }
click to toggle source
# File lib/nswtopo/gis/arcgis_server.rb, line 42 def arcgis_layer(url, where: nil, layer: nil, per_page: nil, margin: {}) ArcGISServer.start url do |connection, service, projection, id| id = service["layers"].find do |info| layer.to_s == info["name"] end&.dig("id") if layer id ? nil : layer ? raise("no such ArcGIS layer: %s" % layer) : raise("not an ArcGIS layer url: %s" % url) layer = connection.get_json id.to_s query_path = "#{id}/query" max_record_count, fields, types, type_id_field, geometry_type, capabilities = layer.values_at "maxRecordCount", "fields", "types", "typeIdField", "geometryType", "capabilities" raise Error, "no query capability available: #{url}" unless capabilities =~ /Query|Data/ if type_id_field && types type_id_field = fields.find do |field| field.values_at("alias", "name").include? type_id_field end&.fetch("name") type_values = types.map do |type| type.values_at "id", "name" end.to_h subtype_coded_values = types.map do |type| type.values_at "id", "domains" end.map do |id, domains| coded_values = domains.map do |name, domain| [name, domain["codedValues"]] end.select(&:last).map do |name, pairs| values = pairs.map do |pair| pair.values_at "code", "name" end.to_h [name, values] end.to_h [id, coded_values] end.to_h end coded_values = fields.map do |field| [field["name"], field.dig("domain", "codedValues")] end.select(&:last).map do |name, pairs| values = pairs.map do |pair| pair.values_at "code", "name" end.to_h [name, values] end.to_h geometry = { rings: @map.bounding_box(margin).reproject_to(projection).coordinates.map(&:reverse) }.to_json where = Array(where).map { |clause| "(#{clause})"}.join " AND " query = { geometry: geometry, geometryType: "esriGeometryPolygon", returnIdsOnly: true, where: where } object_ids = connection.get_json(query_path, query)["objectIds"] next GeoJSON::Collection.new projection unless object_ids features = Enumerator.new do |yielder| per_page, total = [*per_page, *max_record_count, 500].min, object_ids.length while object_ids.any? yield total - object_ids.length, total if block_given? && total > 0 yielder << begin connection.get_json query_path, outFields: ?*, objectIds: object_ids.take(per_page).join(?,) rescue Error => error (per_page /= 2) > 0 ? retry : raise(error) end object_ids.shift per_page end end.inject [] do |features, page| features += page["features"] end.map do |feature| next unless geometry = feature["geometry"] attributes = feature.fetch "attributes", {} values = attributes.map do |name, value| case when type_id_field == name type_values[value] when decode = subtype_coded_values&.dig(attributes[type_id_field], name) decode[value] when decode = coded_values.dig(name) decode[value] when %w[null Null NULL <null> <Null> <NULL>].include?(value) nil else value end end attributes = attributes.keys.zip(values).to_h case geometry_type when "esriGeometryPoint" point = geometry.values_at "x", "y" next unless point.all? next GeoJSON::Point.new point, attributes when "esriGeometryMultipoint" points = geometry["points"] next unless points&.any? next GeoJSON::MultiPoint.new points.transpose.take(2).transpose, attributes when "esriGeometryPolyline" raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curvePaths" paths = geometry["paths"] next unless paths&.any? next GeoJSON::LineString.new paths[0], attributes if paths.one? next GeoJSON::MultiLineString.new paths, attributes when "esriGeometryPolygon" raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curveRings" rings = geometry["rings"] next unless rings&.any? rings.each(&:reverse!) unless rings[0].anticlockwise? next GeoJSON::Polygon.new rings, attributes if rings.one? next GeoJSON::MultiPolygon.new rings.slice_before(&:anticlockwise?).to_a, attributes else raise Error, "unsupported ArcGIS geometry type: #{geometry_type}" end end.compact GeoJSON::Collection.new projection, features end end