nswtopo 2.0.0.pre.beta1 → 3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +70 -83
  3. data/bin/nswtopo +227 -116
  4. data/docs/README.md +1 -12
  5. data/docs/add.md +1 -1
  6. data/docs/config.md +1 -1
  7. data/docs/contours.md +3 -1
  8. data/docs/init.md +8 -0
  9. data/docs/inspect.md +103 -0
  10. data/docs/move.md +9 -0
  11. data/docs/render.md +16 -7
  12. data/docs/scrape.md +67 -0
  13. data/docs/spot-heights.md +6 -2
  14. data/lib/nswtopo/archive.rb +50 -41
  15. data/lib/nswtopo/chrome.rb +227 -0
  16. data/lib/nswtopo/commands/add.rb +106 -0
  17. data/lib/nswtopo/commands/config.rb +38 -0
  18. data/lib/nswtopo/commands/inspect.rb +74 -0
  19. data/lib/nswtopo/commands/layers.rb +22 -0
  20. data/lib/nswtopo/commands/scrape.rb +79 -0
  21. data/lib/nswtopo/commands.rb +57 -0
  22. data/lib/nswtopo/dither.rb +5 -3
  23. data/lib/nswtopo/font.rb +46 -21
  24. data/lib/nswtopo/formats/gemf.rb +42 -0
  25. data/lib/nswtopo/formats/kmz.rb +26 -24
  26. data/lib/nswtopo/formats/mbtiles.rb +5 -41
  27. data/lib/nswtopo/formats/pdf.rb +82 -17
  28. data/lib/nswtopo/formats/svg.rb +114 -45
  29. data/lib/nswtopo/formats/svgz.rb +2 -2
  30. data/lib/nswtopo/formats/zip.rb +33 -23
  31. data/lib/nswtopo/formats.rb +77 -32
  32. data/lib/nswtopo/geometry/overlap.rb +1 -32
  33. data/lib/nswtopo/geometry/r_tree.rb +16 -10
  34. data/lib/nswtopo/geometry/segment.rb +3 -3
  35. data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +5 -6
  36. data/lib/nswtopo/geometry/vector_sequence.rb +7 -6
  37. data/lib/nswtopo/gis/arcgis/connection.rb +56 -0
  38. data/lib/nswtopo/gis/arcgis/layer/map.rb +163 -0
  39. data/lib/nswtopo/gis/arcgis/layer/query.rb +87 -0
  40. data/lib/nswtopo/gis/arcgis/layer/renderer.rb +66 -0
  41. data/lib/nswtopo/gis/arcgis/layer/statistics.rb +15 -0
  42. data/lib/nswtopo/gis/arcgis/layer.rb +201 -0
  43. data/lib/nswtopo/gis/arcgis/service.rb +57 -0
  44. data/lib/nswtopo/gis/arcgis.rb +3 -0
  45. data/lib/nswtopo/gis/dem.rb +13 -12
  46. data/lib/nswtopo/gis/esri_hdr.rb +8 -2
  47. data/lib/nswtopo/gis/geojson/collection.rb +45 -21
  48. data/lib/nswtopo/gis/geojson/multi_line_string.rb +2 -24
  49. data/lib/nswtopo/gis/geojson/multi_polygon.rb +2 -53
  50. data/lib/nswtopo/gis/geojson/polygon.rb +15 -0
  51. data/lib/nswtopo/gis/geojson.rb +12 -3
  52. data/lib/nswtopo/gis/gps/kml.rb +25 -19
  53. data/lib/nswtopo/gis/gps.rb +2 -0
  54. data/lib/nswtopo/gis/projection.rb +35 -24
  55. data/lib/nswtopo/gis/shapefile.rb +89 -16
  56. data/lib/nswtopo/gis.rb +1 -2
  57. data/lib/nswtopo/helpers/array.rb +0 -11
  58. data/lib/nswtopo/helpers/colour.rb +34 -14
  59. data/lib/nswtopo/layer/arcgis_raster.rb +44 -48
  60. data/lib/nswtopo/layer/colour_mask.rb +5 -0
  61. data/lib/nswtopo/layer/contour.rb +35 -28
  62. data/lib/nswtopo/layer/control.rb +2 -7
  63. data/lib/nswtopo/layer/declination.rb +9 -9
  64. data/lib/nswtopo/layer/feature.rb +36 -22
  65. data/lib/nswtopo/layer/grid.rb +30 -27
  66. data/lib/nswtopo/layer/import.rb +1 -21
  67. data/lib/nswtopo/layer/labels/barrier.rb +39 -0
  68. data/lib/nswtopo/layer/labels.rb +551 -383
  69. data/lib/nswtopo/layer/mask_render.rb +37 -0
  70. data/lib/nswtopo/layer/overlay.rb +2 -2
  71. data/lib/nswtopo/layer/raster.rb +31 -41
  72. data/lib/nswtopo/layer/raster_import.rb +17 -0
  73. data/lib/nswtopo/layer/raster_render.rb +15 -0
  74. data/lib/nswtopo/layer/relief.rb +27 -95
  75. data/lib/nswtopo/layer/spot.rb +63 -62
  76. data/lib/nswtopo/layer/vector/cutout.rb +15 -0
  77. data/lib/nswtopo/layer/vector/knockout.rb +16 -0
  78. data/lib/nswtopo/layer/vector.rb +121 -89
  79. data/lib/nswtopo/layer/vegetation.rb +39 -34
  80. data/lib/nswtopo/layer.rb +30 -16
  81. data/lib/nswtopo/map.rb +202 -109
  82. data/lib/nswtopo/os.rb +5 -27
  83. data/lib/nswtopo/tiled_web_map.rb +54 -0
  84. data/lib/nswtopo/tree_indenter.rb +27 -0
  85. data/lib/nswtopo/version.rb +27 -2
  86. data/lib/nswtopo.rb +6 -199
  87. metadata +41 -22
  88. data/lib/nswtopo/font/chrome.rb +0 -59
  89. data/lib/nswtopo/font/generic.rb +0 -25
  90. data/lib/nswtopo/gis/arcgis_server/connection.rb +0 -52
  91. data/lib/nswtopo/gis/arcgis_server.rb +0 -155
  92. data/lib/nswtopo/gis/geojson/multi_point.rb +0 -12
  93. data/lib/nswtopo/gis/world_file.rb +0 -19
  94. data/lib/nswtopo/layer/labels/fence.rb +0 -20
data/lib/nswtopo.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'date'
2
+ require 'time'
2
3
  require 'open3'
3
4
  require 'uri'
4
5
  require 'net/http'
@@ -17,11 +18,7 @@ require 'ostruct'
17
18
  require 'forwardable'
18
19
  require 'rubygems/package'
19
20
  require 'zlib'
20
- begin
21
- require 'pty'
22
- require 'expect'
23
- rescue LoadError
24
- end
21
+ require 'io/nonblock'
25
22
 
26
23
  require_relative 'nswtopo/helpers'
27
24
  require_relative 'nswtopo/avl_tree'
@@ -31,216 +28,26 @@ require_relative 'nswtopo/safely'
31
28
  require_relative 'nswtopo/os'
32
29
  require_relative 'nswtopo/dither'
33
30
  require_relative 'nswtopo/zip'
31
+ require_relative 'nswtopo/chrome'
34
32
  require_relative 'nswtopo/font'
35
33
  require_relative 'nswtopo/archive'
36
34
  require_relative 'nswtopo/gis'
35
+ require_relative 'nswtopo/tiled_web_map'
37
36
  require_relative 'nswtopo/formats'
38
37
  require_relative 'nswtopo/map'
39
38
  require_relative 'nswtopo/layer'
40
39
  require_relative 'nswtopo/version'
41
40
  require_relative 'nswtopo/config'
41
+ require_relative 'nswtopo/commands'
42
+ require_relative 'nswtopo/tree_indenter'
42
43
 
43
44
  module NSWTopo
44
45
  PartialFailureError = Class.new RuntimeError
45
46
  extend self, Log
46
47
 
47
- def init(archive, options)
48
- puts Map.init(archive, options)
49
- end
50
-
51
48
  def layer_dirs
52
49
  @layer_dirs ||= Array(Config["layer-dir"]).map(&Pathname.method(:new)) << Pathname.pwd
53
50
  end
54
-
55
- def info(archive, options)
56
- puts Map.load(archive).info(options)
57
- end
58
-
59
- def add(archive, *layers, options)
60
- create_options = {
61
- after: Layer.sanitise(options.delete :after),
62
- before: Layer.sanitise(options.delete :before),
63
- replace: Layer.sanitise(options.delete :replace),
64
- overwrite: options.delete(:overwrite)
65
- }
66
- map = Map.load archive
67
-
68
- Enumerator.new do |yielder|
69
- while layers.any?
70
- layer, basedir = layers.shift
71
- path = Pathname(layer).expand_path(*basedir)
72
- case layer
73
- when /^controls\.(gpx|kml)$/i
74
- yielder << [path.basename(path.extname).to_s, "type" => "Control", "path" => path]
75
- when /\.(gpx|kml)$/i
76
- yielder << [path.basename(path.extname).to_s, "type" => "Overlay", "path" => path]
77
- when /\.(tiff?|png|jpg)$/i
78
- yielder << [path.basename(path.extname).to_s, "type" => "Import", "path" => path]
79
- when "contours"
80
- yielder << [layer, "type" => "Contour"]
81
- when "spot-heights"
82
- yielder << [layer, "type" => "Spot"]
83
- when "relief"
84
- yielder << [layer, "type" => "Relief"]
85
- when "grid"
86
- yielder << [layer, "type" => "Grid"]
87
- when "declination"
88
- yielder << [layer, "type" => "Declination"]
89
- when "controls"
90
- yielder << [layer, "type" => "Control"]
91
- when /\.yml$/i
92
- basedir ||= path.parent
93
- raise "couldn't find '#{layer}'" unless path.file?
94
- case contents = YAML.load(path.read)
95
- when Array
96
- contents.reverse.map do |item|
97
- Pathname(item.to_s)
98
- end.each do |relative_path|
99
- raise "#{relative_path} is not a relative path" unless relative_path.relative?
100
- layers.prepend [Pathname(relative_path).expand_path(path.parent).relative_path_from(basedir).to_s, basedir]
101
- end
102
- when Hash
103
- name = path.sub_ext("").relative_path_from(basedir).descend.map(&:basename).join(?.)
104
- yielder << [name, contents.merge("source" => path)]
105
- else
106
- raise "couldn't parse #{path}"
107
- end
108
- else
109
- path = Pathname("#{layer}.yml")
110
- raise "#{layer} is not a relative path" unless path.relative?
111
- basedir ||= layer_dirs.find do |root|
112
- path.expand_path(root).file?
113
- end
114
- layers.prepend [path.to_s, basedir]
115
- end
116
- end
117
- rescue YAML::Exception
118
- raise "couldn't parse #{path}"
119
- end.map do |name, params|
120
- params.merge! options.transform_keys(&:to_s)
121
- params.merge! Config[name] if Config[name]
122
- Layer.new(name, map, params)
123
- end.tap do |layers|
124
- raise OptionParser::MissingArgument, "no layers specified" unless layers.any?
125
- unless layers.one?
126
- raise OptionParser::InvalidArgument, "can't specify resolution when adding multiple layers" if options[:resolution]
127
- raise OptionParser::InvalidArgument, "can't specify data path when adding multiple layers" if options[:path]
128
- end
129
- map.add *layers, create_options
130
- end
131
- end
132
-
133
- def contours(archive, dem_path, options)
134
- add archive, "contours", options.merge(path: Pathname(dem_path))
135
- end
136
-
137
- def spot_heights(archive, dem_path, options)
138
- add archive, "spot-heights", options.merge(path: Pathname(dem_path))
139
- end
140
-
141
- def relief(archive, dem_path, options)
142
- add archive, "relief", options.merge(path: Pathname(dem_path))
143
- end
144
-
145
- def grid(archive, options)
146
- add archive, "grid", options
147
- end
148
-
149
- def declination(archive, options)
150
- add archive, "declination", options
151
- end
152
-
153
- def controls(archive, gps_path, options)
154
- add archive, "controls", options.merge(path: Pathname(gps_path))
155
- end
156
-
157
- def overlay(archive, gps_path, options)
158
- raise OptionParser::InvalidArgument, gps_path unless gps_path =~ /\.(gpx|kml)$/i
159
- add archive, gps_path, options.merge(path: Pathname(gps_path))
160
- end
161
-
162
- def delete(archive, *names, options)
163
- map = Map.load archive
164
- names.map do |name|
165
- Layer.sanitise name
166
- end.uniq.map do |name|
167
- name[?*] ? %r[^#{name.gsub(?., '\.').gsub(?*, '.*')}$] : name
168
- end.tap do |names|
169
- map.delete *names
170
- end
171
- end
172
-
173
- def render(archive, *formats, options)
174
- overwrite = options.delete :overwrite
175
- formats << "svg" if formats.empty?
176
- formats.map do |format|
177
- Pathname(Formats === format ? "#{archive.basename}.#{format}" : format)
178
- end.uniq.each do |path|
179
- format = path.extname.delete_prefix(?.)
180
- raise "unrecognised format: #{path}" if format.empty?
181
- raise "unrecognised format: #{format}" unless Formats === format
182
- raise "file already exists: #{path}" if path.exist? && !overwrite
183
- raise "non-existent directory: #{path.parent}" unless path.parent.directory?
184
- end.tap do |paths|
185
- Map.load(archive).render *paths, options
186
- end
187
- end
188
-
189
- def layers(state: nil, indent: "")
190
- layer_dirs.grep_v(Pathname.pwd).flat_map do |directory|
191
- Array(state).inject(directory, &:/).glob("*")
192
- end.sort.each do |path|
193
- case
194
- when path.directory?
195
- next if path.glob("**/*.yml").none?
196
- puts [indent, path.basename.sub_ext("")].join
197
- layers state: [*state, path.basename], indent: " " + indent
198
- when path.sub_ext("").directory?
199
- when path.extname == ".yml"
200
- puts [indent, path.basename.sub_ext("")].join
201
- end
202
- end.tap do |paths|
203
- log_warn "no layers installed" if paths.none?
204
- end
205
- end
206
-
207
- def config(layer = nil, **options)
208
- chrome, firefox, path, resolution, layer_dir, labelling, delete = options.values_at :chrome, :firefox, :path, :resolution, :"layer-dir", :labelling, :delete
209
- raise "not a directory: %s" % layer_dir if layer_dir && !layer_dir.directory?
210
- raise "chrome path is not an executable" if chrome && !chrome.executable?
211
- raise "firefox path is not an executable" if firefox && !firefox.executable?
212
- Config.store("chrome", chrome.to_s) if chrome
213
- Config.store("firefox", firefox.to_s) if firefox
214
- Config.store("labelling", labelling) unless labelling.nil?
215
- Config.store("layer-dir", layer_dir.to_s) if layer_dir
216
-
217
- layer = Layer.sanitise layer
218
- case
219
- when !layer
220
- raise OptionParser::InvalidArgument, "no layer name specified for path" if path
221
- raise OptionParser::InvalidArgument, "no layer name specified for resolution" if resolution
222
- when path || resolution
223
- Config.store(layer, "path", path.to_s) if path
224
- Config.store(layer, "resolution", resolution) if resolution
225
- end
226
- Config.delete(*layer, delete) if delete
227
-
228
- if options.empty?
229
- puts Config.to_str.each_line.drop(1)
230
- log_neutral "no configuration yet" if Config.empty?
231
- else
232
- Config.save
233
- log_success "configuration updated"
234
- end
235
- end
236
-
237
- def with_browser
238
- browser_name, browser_path = Config.slice("chrome", "firefox").first
239
- raise "please configure a path for google chrome" unless browser_name
240
- yield browser_name, Pathname.new(browser_path)
241
- rescue Errno::ENOENT
242
- raise "invalid %s path: %s" % [browser_name, browser_path]
243
- end
244
51
  end
245
52
 
246
53
  begin
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nswtopo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.beta1
4
+ version: '3.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Hollingworth
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-13 00:00:00.000000000 Z
11
+ date: 2023-08-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description:
14
- email:
13
+ description:
14
+ email:
15
15
  executables:
16
16
  - nswtopo
17
17
  extensions: []
@@ -29,20 +29,29 @@ files:
29
29
  - docs/grid.md
30
30
  - docs/info.md
31
31
  - docs/init.md
32
+ - docs/inspect.md
32
33
  - docs/layers.md
34
+ - docs/move.md
33
35
  - docs/overlay.md
34
36
  - docs/relief.md
35
37
  - docs/render.md
38
+ - docs/scrape.md
36
39
  - docs/spot-heights.md
37
40
  - lib/nswtopo.rb
38
41
  - lib/nswtopo/archive.rb
39
42
  - lib/nswtopo/avl_tree.rb
43
+ - lib/nswtopo/chrome.rb
44
+ - lib/nswtopo/commands.rb
45
+ - lib/nswtopo/commands/add.rb
46
+ - lib/nswtopo/commands/config.rb
47
+ - lib/nswtopo/commands/inspect.rb
48
+ - lib/nswtopo/commands/layers.rb
49
+ - lib/nswtopo/commands/scrape.rb
40
50
  - lib/nswtopo/config.rb
41
51
  - lib/nswtopo/dither.rb
42
52
  - lib/nswtopo/font.rb
43
- - lib/nswtopo/font/chrome.rb
44
- - lib/nswtopo/font/generic.rb
45
53
  - lib/nswtopo/formats.rb
54
+ - lib/nswtopo/formats/gemf.rb
46
55
  - lib/nswtopo/formats/kmz.rb
47
56
  - lib/nswtopo/formats/mbtiles.rb
48
57
  - lib/nswtopo/formats/pdf.rb
@@ -63,8 +72,14 @@ files:
63
72
  - lib/nswtopo/geometry/vector.rb
64
73
  - lib/nswtopo/geometry/vector_sequence.rb
65
74
  - lib/nswtopo/gis.rb
66
- - lib/nswtopo/gis/arcgis_server.rb
67
- - lib/nswtopo/gis/arcgis_server/connection.rb
75
+ - lib/nswtopo/gis/arcgis.rb
76
+ - lib/nswtopo/gis/arcgis/connection.rb
77
+ - lib/nswtopo/gis/arcgis/layer.rb
78
+ - lib/nswtopo/gis/arcgis/layer/map.rb
79
+ - lib/nswtopo/gis/arcgis/layer/query.rb
80
+ - lib/nswtopo/gis/arcgis/layer/renderer.rb
81
+ - lib/nswtopo/gis/arcgis/layer/statistics.rb
82
+ - lib/nswtopo/gis/arcgis/service.rb
68
83
  - lib/nswtopo/gis/dem.rb
69
84
  - lib/nswtopo/gis/esri_hdr.rb
70
85
  - lib/nswtopo/gis/gdal_glob.rb
@@ -72,7 +87,6 @@ files:
72
87
  - lib/nswtopo/gis/geojson/collection.rb
73
88
  - lib/nswtopo/gis/geojson/line_string.rb
74
89
  - lib/nswtopo/gis/geojson/multi_line_string.rb
75
- - lib/nswtopo/gis/geojson/multi_point.rb
76
90
  - lib/nswtopo/gis/geojson/multi_polygon.rb
77
91
  - lib/nswtopo/gis/geojson/point.rb
78
92
  - lib/nswtopo/gis/geojson/polygon.rb
@@ -81,7 +95,6 @@ files:
81
95
  - lib/nswtopo/gis/gps/kml.rb
82
96
  - lib/nswtopo/gis/projection.rb
83
97
  - lib/nswtopo/gis/shapefile.rb
84
- - lib/nswtopo/gis/world_file.rb
85
98
  - lib/nswtopo/help_formatter.rb
86
99
  - lib/nswtopo/helpers.rb
87
100
  - lib/nswtopo/helpers/array.rb
@@ -92,6 +105,7 @@ files:
92
105
  - lib/nswtopo/helpers/tar_writer.rb
93
106
  - lib/nswtopo/layer.rb
94
107
  - lib/nswtopo/layer/arcgis_raster.rb
108
+ - lib/nswtopo/layer/colour_mask.rb
95
109
  - lib/nswtopo/layer/contour.rb
96
110
  - lib/nswtopo/layer/control.rb
97
111
  - lib/nswtopo/layer/declination.rb
@@ -99,24 +113,31 @@ files:
99
113
  - lib/nswtopo/layer/grid.rb
100
114
  - lib/nswtopo/layer/import.rb
101
115
  - lib/nswtopo/layer/labels.rb
102
- - lib/nswtopo/layer/labels/fence.rb
116
+ - lib/nswtopo/layer/labels/barrier.rb
117
+ - lib/nswtopo/layer/mask_render.rb
103
118
  - lib/nswtopo/layer/overlay.rb
104
119
  - lib/nswtopo/layer/raster.rb
120
+ - lib/nswtopo/layer/raster_import.rb
121
+ - lib/nswtopo/layer/raster_render.rb
105
122
  - lib/nswtopo/layer/relief.rb
106
123
  - lib/nswtopo/layer/spot.rb
107
124
  - lib/nswtopo/layer/vector.rb
125
+ - lib/nswtopo/layer/vector/cutout.rb
126
+ - lib/nswtopo/layer/vector/knockout.rb
108
127
  - lib/nswtopo/layer/vegetation.rb
109
128
  - lib/nswtopo/log.rb
110
129
  - lib/nswtopo/map.rb
111
130
  - lib/nswtopo/os.rb
112
131
  - lib/nswtopo/safely.rb
132
+ - lib/nswtopo/tiled_web_map.rb
133
+ - lib/nswtopo/tree_indenter.rb
113
134
  - lib/nswtopo/version.rb
114
135
  - lib/nswtopo/zip.rb
115
136
  homepage: https://github.com/mholling/nswtopo
116
137
  licenses:
117
138
  - GPL-3.0
118
139
  metadata: {}
119
- post_install_message:
140
+ post_install_message:
120
141
  rdoc_options: []
121
142
  require_paths:
122
143
  - lib
@@ -124,19 +145,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
145
  requirements:
125
146
  - - ">="
126
147
  - !ruby/object:Gem::Version
127
- version: 2.5.0
148
+ version: 3.0.4
128
149
  required_rubygems_version: !ruby/object:Gem::Requirement
129
150
  requirements:
130
- - - ">"
151
+ - - ">="
131
152
  - !ruby/object:Gem::Version
132
- version: 1.3.1
153
+ version: '0'
133
154
  requirements:
134
- - GDAL >= v2.3
135
- - ImageMagick
136
- - Google Chrome
137
- rubyforge_project:
138
- rubygems_version: 2.7.6
139
- signing_key:
155
+ - GDAL >= v3.4
156
+ - Google Chrome >= v112
157
+ rubygems_version: 3.2.33
158
+ signing_key:
140
159
  specification_version: 4
141
160
  summary: A vector topographic mapping tool
142
161
  test_files: []
@@ -1,59 +0,0 @@
1
- module NSWTopo
2
- module Font
3
- module Chrome
4
- ATTRIBUTES = %w[font-family font-variant font-style font-weight font-size letter-spacing word-spacing]
5
-
6
- def command(string)
7
- @input.puts string
8
- lines, match = @output.expect(/(\{.*)\n/, 1)
9
- response = JSON.parse match
10
- raise "unexpected chrome error: %s" % response.dig("exceptionDetails", "exception", "description") if response["exceptionDetails"]
11
- response.fetch("result").dig("value")
12
- rescue TypeError, JSON::ParserError, KeyError
13
- raise "unexpected chrome error"
14
- end
15
-
16
- def start_chrome
17
- chrome_path = Config["chrome"]
18
- svg = <<~XML
19
- <?xml version='1.0' encoding='UTF-8'?>
20
- <svg version='1.1' baseProfile='full' xmlns='http://www.w3.org/2000/svg' width='1mm' height='1mm' viewBox='0 0 1 1'>
21
- <rect id='mm' width='1' height='1' stroke='none' />
22
- <text id='text' />
23
- </svg>
24
- XML
25
- @output, @input, @pid = PTY.spawn chrome_path, "--headless", "--disable-gpu", "--repl", "data:image/svg+xml;base64,#{Base64.encode64 svg}"
26
- ObjectSpace.define_finalizer self, Proc.new { @input.puts "quit" }
27
- command %Q[text = document.getElementById("text")]
28
- @mm = command %Q[document.getElementById("mm").getBoundingClientRect().width]
29
- end
30
-
31
- def self.extended(instance)
32
- instance.start_chrome
33
- end
34
-
35
- def validate(family)
36
- return unless family
37
- @families ||= Set[]
38
- @families.add?(family) || return
39
- command %Q[text.textContent="abcdefghijklmnopqrstuvwxyz"]
40
- ["font-family:#{family}", nil].map do |style|
41
- command %Q[text.setAttribute("style", "#{style}")]
42
- command %Q[text.getBoundingClientRect().width]
43
- end.inject(&:==) || return
44
- log_neutral "font '#{family}' doesn't appear to be available"
45
- end
46
-
47
- def glyph_length(string, attributes)
48
- style = attributes.slice(*ATTRIBUTES).map do |pair|
49
- pair.join ?:
50
- end.join(?;)
51
- style << ";white-space:pre" if ?\s == string
52
- validate attributes["font-family"]
53
- command %Q[text.setAttribute("style", #{style.inspect})]
54
- command %Q[text.textContent=#{string.inspect}]
55
- command(%Q[text.getBoundingClientRect().width]) / @mm
56
- end
57
- end
58
- end
59
- end
@@ -1,25 +0,0 @@
1
- module NSWTopo
2
- module Font
3
- module Generic
4
- WIDTHS = {
5
- ?A => 0.732, ?B => 0.678, ?C => 0.682, ?D => 0.740, ?E => 0.583, ?F => 0.558, ?G => 0.728, ?H => 0.761, ?I => 0.256, ?J => 0.331, ?K => 0.641, ?L => 0.542, ?M => 0.843,
6
- ?N => 0.740, ?O => 0.769, ?P => 0.649, ?Q => 0.769, ?R => 0.690, ?S => 0.620, ?T => 0.599, ?U => 0.728, ?V => 0.695, ?W => 1.108, ?X => 0.649, ?Y => 0.637, ?Z => 0.591,
7
- ?a => 0.595, ?b => 0.595, ?c => 0.492, ?d => 0.595, ?e => 0.542, ?f => 0.335, ?g => 0.599, ?h => 0.583, ?i => 0.236, ?j => 0.289, ?k => 0.521, ?l => 0.236, ?m => 0.876,
8
- ?n => 0.583, ?o => 0.571, ?p => 0.595, ?q => 0.595, ?r => 0.360, ?s => 0.492, ?t => 0.347, ?u => 0.575, ?v => 0.529, ?w => 0.864, ?x => 0.533, ?y => 0.529, ?z => 0.513,
9
- ?0 => 0.595, ?1 => 0.595, ?2 => 0.595, ?3 => 0.595, ?4 => 0.595, ?5 => 0.595, ?6 => 0.595, ?7 => 0.595, ?8 => 0.595, ?9 => 0.595, ?! => 0.227, ?" => 0.422, ?# => 0.604,
10
- ?$ => 0.595, ?% => 0.934, ?& => 0.678, ?' => 0.219, ?( => 0.314, ?) => 0.314, ?* => 0.451, ?+ => 0.595, ?, => 0.227, ?- => 0.426, ?. => 0.227, ?/ => 0.331, ?\\ => 0.327,
11
- ?[ => 0.314, ?] => 0.314, ?^ => 0.595, ?_ => 0.500, ?` => 0.310, ?: => 0.227, ?; => 0.227, ?< => 0.595, ?= => 0.595, ?> => 0.595, ?? => 0.442, ?@ => 0.930, ?\s => 0.265,
12
- }
13
- WIDTHS.default = WIDTHS[?M]
14
-
15
- def glyph_length(string, attributes)
16
- font_size, letter_spacing, word_spacing = attributes.values_at("font-size", "letter-spacing", "word-spacing").map(&:to_f)
17
- string.chars.each_cons(2).inject(WIDTHS[string[0]] * font_size) do |sum, pair|
18
- next sum + WIDTHS[pair[1]] * font_size + letter_spacing unless pair[0] == ?\s
19
- next sum + WIDTHS[pair[1]] * font_size + letter_spacing + word_spacing unless pair[1] == ?\s
20
- sum
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,52 +0,0 @@
1
- module NSWTopo
2
- module ArcGISServer
3
- class Connection
4
- def initialize(http, service_path)
5
- @http, @service_path, @headers = http, service_path, { "User-Agent" => "Ruby/#{RUBY_VERSION}", "Referer" => "%s://%s" % [http.use_ssl? ? "https" : "http", http.address] }
6
- http.max_retries = 0
7
- end
8
-
9
- def repeatedly_request(request)
10
- intervals ||= 5.times.map(&1.4142.method(:**))
11
- response = @http.request(request)
12
- response.error! unless Net::HTTPSuccess === response
13
- yield response
14
- rescue *ERRORS, Error => error
15
- interval = intervals.shift
16
- interval ? sleep(interval) : raise(error)
17
- retry
18
- end
19
-
20
- def get(relative_path, **query, &block)
21
- path = Pathname(@service_path).join(relative_path).to_s
22
- path << ?? << URI.encode_www_form(query) unless query.empty?
23
- request = Net::HTTP::Get.new(path, @headers)
24
- repeatedly_request(request, &block)
25
- end
26
-
27
- def post(relative_path, **query, &block)
28
- path = Pathname(@service_path).join(relative_path).to_s
29
- request = Net::HTTP::Post.new(path, @headers)
30
- request.body = URI.encode_www_form(query)
31
- repeatedly_request(request, &block)
32
- end
33
-
34
- def process_json(response)
35
- JSON.parse(response.body).tap do |result|
36
- # raise Error, result["error"].values_at("message", "details").compact.join(?\n) if result["error"]
37
- raise Error, result["error"]["message"] if result["error"]
38
- end
39
- rescue JSON::ParserError
40
- raise Error, "unexpected ArcGIS response format"
41
- end
42
-
43
- def get_json(relative_path = "", **query)
44
- get relative_path, query.merge(f: "json"), &method(:process_json)
45
- end
46
-
47
- def post_json(relative_path = "", **query)
48
- post relative_path, query.merge(f: "json"), &method(:process_json)
49
- end
50
- end
51
- end
52
- end
@@ -1,155 +0,0 @@
1
- require_relative 'arcgis_server/connection'
2
-
3
- module NSWTopo
4
- module ArcGISServer
5
- Error = Class.new RuntimeError
6
- ERRORS = [Timeout::Error, Errno::ENETUNREACH, Errno::ETIMEDOUT, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, SocketError]
7
- SERVICE = /^(?:MapServer|FeatureServer|ImageServer)$/
8
-
9
- def self.check_uri(url)
10
- uri = URI.parse url
11
- return unless URI::HTTP === uri
12
- instance, (id, *) = uri.path.split(?/).slice_after(SERVICE).take(2)
13
- return unless instance.last =~ SERVICE
14
- return unless !id || id =~ /^\d+$/
15
- return uri, instance.join(?/), id
16
- rescue URI::Error
17
- end
18
-
19
- def self.===(string)
20
- uri, service_path, id = check_uri string
21
- uri != nil
22
- end
23
-
24
- def self.start(url, &block)
25
- uri, service_path, id = check_uri url
26
- raise "invalid ArcGIS server URL: %s" % url unless uri
27
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: 600) do |http|
28
- connection = Connection.new http, service_path
29
- service = connection.get_json
30
- projection = case
31
- when wkt = service.dig("spatialReference", "wkt") then Projection.new(wkt)
32
- when wkid = service.dig("spatialReference", "latestWkid") then Projection.new("EPSG:#{wkid}")
33
- when wkid = service.dig("spatialReference", "wkid") then Projection.new("EPSG:#{wkid == 102100 ? 3857 : wkid}")
34
- else raise Error, "no spatial reference found: #{uri}"
35
- end
36
- yield connection, service, projection, *id
37
- end
38
- rescue *ERRORS => error
39
- raise Error, error.message
40
- end
41
-
42
- def arcgis_layer(url, where: nil, layer: nil, per_page: nil, margin: {})
43
- ArcGISServer.start url do |connection, service, projection, id|
44
- id = service["layers"].find do |info|
45
- layer.to_s == info["name"]
46
- end&.dig("id") if layer
47
- id ? nil : layer ? raise("no such ArcGIS layer: %s" % layer) : raise("not an ArcGIS layer url: %s" % url)
48
-
49
- layer = connection.get_json id.to_s
50
- query_path = "#{id}/query"
51
- max_record_count, fields, types, type_id_field, geometry_type, capabilities = layer.values_at "maxRecordCount", "fields", "types", "typeIdField", "geometryType", "capabilities"
52
- raise Error, "no query capability available: #{url}" unless capabilities =~ /Query|Data/
53
-
54
- if type_id_field && types
55
- type_id_field = fields.find do |field|
56
- field.values_at("alias", "name").include? type_id_field
57
- end&.fetch("name")
58
- type_values = types.map do |type|
59
- type.values_at "id", "name"
60
- end.to_h
61
- subtype_coded_values = types.map do |type|
62
- type.values_at "id", "domains"
63
- end.map do |id, domains|
64
- coded_values = domains.map do |name, domain|
65
- [name, domain["codedValues"]]
66
- end.select(&:last).map do |name, pairs|
67
- values = pairs.map do |pair|
68
- pair.values_at "code", "name"
69
- end.to_h
70
- [name, values]
71
- end.to_h
72
- [id, coded_values]
73
- end.to_h
74
- end
75
-
76
- coded_values = fields.map do |field|
77
- [field["name"], field.dig("domain", "codedValues")]
78
- end.select(&:last).map do |name, pairs|
79
- values = pairs.map do |pair|
80
- pair.values_at "code", "name"
81
- end.to_h
82
- [name, values]
83
- end.to_h
84
-
85
- geometry = { rings: @map.bounding_box(margin).reproject_to(projection).coordinates.map(&:reverse) }.to_json
86
- where = Array(where).map { |clause| "(#{clause})"}.join " AND "
87
- query = { geometry: geometry, geometryType: "esriGeometryPolygon", returnIdsOnly: true, where: where }
88
-
89
- object_ids = connection.get_json(query_path, query)["objectIds"]
90
- next GeoJSON::Collection.new projection unless object_ids
91
-
92
- features = Enumerator.new do |yielder|
93
- per_page, total = [*per_page, *max_record_count, 500].min, object_ids.length
94
- while object_ids.any?
95
- yield total - object_ids.length, total if block_given? && total > 0
96
- yielder << begin
97
- connection.get_json query_path, outFields: ?*, objectIds: object_ids.take(per_page).join(?,)
98
- rescue Error => error
99
- (per_page /= 2) > 0 ? retry : raise(error)
100
- end
101
- object_ids.shift per_page
102
- end
103
- end.inject [] do |features, page|
104
- features += page["features"]
105
- end.map do |feature|
106
- next unless geometry = feature["geometry"]
107
- attributes = feature.fetch "attributes", {}
108
-
109
- values = attributes.map do |name, value|
110
- case
111
- when type_id_field == name
112
- type_values[value]
113
- when decode = subtype_coded_values&.dig(attributes[type_id_field], name)
114
- decode[value]
115
- when decode = coded_values.dig(name)
116
- decode[value]
117
- when %w[null Null NULL <null> <Null> <NULL>].include?(value)
118
- nil
119
- else value
120
- end
121
- end
122
- attributes = attributes.keys.zip(values).to_h
123
-
124
- case geometry_type
125
- when "esriGeometryPoint"
126
- point = geometry.values_at "x", "y"
127
- next unless point.all?
128
- next GeoJSON::Point.new point, attributes
129
- when "esriGeometryMultipoint"
130
- points = geometry["points"]
131
- next unless points&.any?
132
- next GeoJSON::MultiPoint.new points.transpose.take(2).transpose, attributes
133
- when "esriGeometryPolyline"
134
- raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curvePaths"
135
- paths = geometry["paths"]
136
- next unless paths&.any?
137
- next GeoJSON::LineString.new paths[0], attributes if paths.one?
138
- next GeoJSON::MultiLineString.new paths, attributes
139
- when "esriGeometryPolygon"
140
- raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curveRings"
141
- rings = geometry["rings"]
142
- next unless rings&.any?
143
- rings.each(&:reverse!) unless rings[0].anticlockwise?
144
- next GeoJSON::Polygon.new rings, attributes if rings.one?
145
- next GeoJSON::MultiPolygon.new rings.slice_before(&:anticlockwise?).to_a, attributes
146
- else
147
- raise Error, "unsupported ArcGIS geometry type: #{geometry_type}"
148
- end
149
- end.compact
150
-
151
- GeoJSON::Collection.new projection, features
152
- end
153
- end
154
- end
155
- end