nswtopo 2.0.0.pre.beta1 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
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